nolo-cli 0.37.0-alpha.2 → 0.37.0-alpha.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/index.js
CHANGED
|
@@ -824,7 +824,7 @@ async function runScript(script, forwardedArgs, env) {
|
|
|
824
824
|
return proc.exited;
|
|
825
825
|
}
|
|
826
826
|
async function launchTuiWorkspace(args2) {
|
|
827
|
-
const { startTuiWorkspace } = await import("./readlineWorkspace-
|
|
827
|
+
const { startTuiWorkspace } = await import("./readlineWorkspace-PLJJPDP2.js");
|
|
828
828
|
const { createTuiSummaryLlmCaller } = await import("./tuiSummaryLlmCaller-SHRB4RWM.js");
|
|
829
829
|
return startTuiWorkspace({
|
|
830
830
|
...args2,
|
package/package.json
CHANGED
|
@@ -4394,6 +4394,24 @@ async function readImagePaths(paths, options = {}) {
|
|
|
4394
4394
|
}
|
|
4395
4395
|
return { images, failures };
|
|
4396
4396
|
}
|
|
4397
|
+
async function resolveAttachmentImageUrls({
|
|
4398
|
+
actionImagePaths,
|
|
4399
|
+
attachedImages,
|
|
4400
|
+
onFailure
|
|
4401
|
+
}) {
|
|
4402
|
+
const pathsToRead = [
|
|
4403
|
+
...actionImagePaths ?? [],
|
|
4404
|
+
...attachedImages.map((img) => img.sourcePath)
|
|
4405
|
+
];
|
|
4406
|
+
let imageUrls = [];
|
|
4407
|
+
if (pathsToRead.length > 0) {
|
|
4408
|
+
const readResult = await readImagePaths(pathsToRead, {
|
|
4409
|
+
onFailure
|
|
4410
|
+
});
|
|
4411
|
+
imageUrls = readResult.images.map((img) => img.dataUrl);
|
|
4412
|
+
}
|
|
4413
|
+
return { imageUrls };
|
|
4414
|
+
}
|
|
4397
4415
|
|
|
4398
4416
|
// packages/cli/tui/clipboardImage.ts
|
|
4399
4417
|
import { createHash } from "node:crypto";
|
|
@@ -7076,6 +7094,219 @@ function waitForRawActionGate(input, output, gate, spawnRunner, hooks) {
|
|
|
7076
7094
|
}
|
|
7077
7095
|
});
|
|
7078
7096
|
}
|
|
7097
|
+
async function runOneAgentTurn(ctx, inputMsg, imageUrls, actionGateHandler, confirmDestructiveAction) {
|
|
7098
|
+
const scheduleTitlePatchSync = (runResult) => {
|
|
7099
|
+
if (!runResult.titlePatchPromise || !runResult.dialogId) return;
|
|
7100
|
+
const patchDialogId = runResult.dialogId;
|
|
7101
|
+
runResult.titlePatchPromise.then((patchedTitle) => {
|
|
7102
|
+
if (!patchedTitle || ctx.sessionEnded) return;
|
|
7103
|
+
if (ctx.state.dialogId !== patchDialogId) return;
|
|
7104
|
+
if (ctx.state.dialogLabel === patchedTitle) return;
|
|
7105
|
+
ctx.state = {
|
|
7106
|
+
...ctx.state,
|
|
7107
|
+
dialogLabel: patchedTitle,
|
|
7108
|
+
dialogTitle: patchedTitle
|
|
7109
|
+
};
|
|
7110
|
+
ctx.syncWindowTitle();
|
|
7111
|
+
}).catch(() => {
|
|
7112
|
+
});
|
|
7113
|
+
};
|
|
7114
|
+
let req = createTurnRequest(inputMsg);
|
|
7115
|
+
if (req.event.kind === "child-run-completed") {
|
|
7116
|
+
const nowMs = Date.now();
|
|
7117
|
+
const { remainingRuns, claimedRunIds } = filterUnclaimedChildRuns(
|
|
7118
|
+
req.event.runs,
|
|
7119
|
+
{ env: ctx.effectiveEnv, now: () => nowMs }
|
|
7120
|
+
);
|
|
7121
|
+
for (const runId of claimedRunIds) {
|
|
7122
|
+
ctx.runCompletionWatcher.markAcknowledged(runId);
|
|
7123
|
+
}
|
|
7124
|
+
if (remainingRuns.length === 0) {
|
|
7125
|
+
for (const r of req.event.runs) {
|
|
7126
|
+
ctx.runCompletionWatcher.markAcknowledged(r.runId);
|
|
7127
|
+
}
|
|
7128
|
+
return { ok: true, aborted: false };
|
|
7129
|
+
}
|
|
7130
|
+
for (const r of remainingRuns) {
|
|
7131
|
+
ctx.runCompletionWatcher.markAcknowledged(r.runId);
|
|
7132
|
+
}
|
|
7133
|
+
if (remainingRuns.length !== req.event.runs.length) {
|
|
7134
|
+
const text = buildWakeMessage(remainingRuns, nowMs);
|
|
7135
|
+
const displayText = buildWakeDisplayText(remainingRuns, nowMs);
|
|
7136
|
+
const updatedEvent = {
|
|
7137
|
+
kind: "child-run-completed",
|
|
7138
|
+
runs: remainingRuns,
|
|
7139
|
+
text,
|
|
7140
|
+
displayText
|
|
7141
|
+
};
|
|
7142
|
+
req = {
|
|
7143
|
+
text,
|
|
7144
|
+
event: updatedEvent
|
|
7145
|
+
};
|
|
7146
|
+
}
|
|
7147
|
+
}
|
|
7148
|
+
const message = req.text;
|
|
7149
|
+
ctx.forcedStop = false;
|
|
7150
|
+
ctx.turnEpoch += 1;
|
|
7151
|
+
const myEpoch = ctx.turnEpoch;
|
|
7152
|
+
ctx.history.followBottom = true;
|
|
7153
|
+
const isInternalEvent = req.event.kind !== "user";
|
|
7154
|
+
const transcriptText = req.event.kind === "child-run-completed" ? req.event.displayText ?? req.event.text : message;
|
|
7155
|
+
startTurn(ctx.history, isInternalEvent ? "assistant" : "user");
|
|
7156
|
+
appendToCurrentTurn(
|
|
7157
|
+
ctx.history,
|
|
7158
|
+
isInternalEvent ? dimCliText(transcriptText, resolveCliColorEnabled()) : transcriptText
|
|
7159
|
+
);
|
|
7160
|
+
finalizeCurrentTurn(ctx.history);
|
|
7161
|
+
ctx.renderHistoryToOutput();
|
|
7162
|
+
if (ctx.fixedInput.active) ctx.fixedInput.repaint(ctx.buffer, ctx.cursorPos);
|
|
7163
|
+
startTurn(ctx.history, "assistant");
|
|
7164
|
+
const agentOutput = isInteractiveInput(ctx.input) ? createHistoryOutputStream(ctx.history, () => {
|
|
7165
|
+
ctx.scheduleRender();
|
|
7166
|
+
}) : ctx.output;
|
|
7167
|
+
const requestUserChoice = isInteractiveInput(ctx.input) && ctx.dialogHost ? async (choiceReq) => {
|
|
7168
|
+
ctx.modalOwnsKeyboard = true;
|
|
7169
|
+
try {
|
|
7170
|
+
return await ctx.dialogHost.run(
|
|
7171
|
+
(anchor) => runAskChoiceDialog({
|
|
7172
|
+
request: choiceReq,
|
|
7173
|
+
input: ctx.input,
|
|
7174
|
+
output: ctx.output,
|
|
7175
|
+
...anchor
|
|
7176
|
+
})
|
|
7177
|
+
);
|
|
7178
|
+
} catch {
|
|
7179
|
+
return { kind: "cancelled" };
|
|
7180
|
+
} finally {
|
|
7181
|
+
ctx.composerDecoderDrain?.();
|
|
7182
|
+
ctx.modalOwnsKeyboard = false;
|
|
7183
|
+
}
|
|
7184
|
+
} : void 0;
|
|
7185
|
+
try {
|
|
7186
|
+
ctx.activeTurnAbort = new AbortController();
|
|
7187
|
+
ctx.activeTurnEpoch = myEpoch;
|
|
7188
|
+
const runResult = await runAgentChat(
|
|
7189
|
+
ctx.options.scriptDir,
|
|
7190
|
+
ctx.state,
|
|
7191
|
+
message,
|
|
7192
|
+
ctx.options.env ?? process.env,
|
|
7193
|
+
agentOutput,
|
|
7194
|
+
ctx.options.agentRunner,
|
|
7195
|
+
{
|
|
7196
|
+
...imageUrls.length > 0 ? { imageUrls } : {},
|
|
7197
|
+
actionGateHandler,
|
|
7198
|
+
...confirmDestructiveAction ? { confirmDestructiveAction } : {},
|
|
7199
|
+
...requestUserChoice ? { requestUserChoice } : {},
|
|
7200
|
+
abortSignal: ctx.activeTurnAbort.signal,
|
|
7201
|
+
pastedTextStore: ctx.pasteStore,
|
|
7202
|
+
activityReporter: ctx.activityReporter,
|
|
7203
|
+
onAgentRunStatus: (snapshot) => {
|
|
7204
|
+
if (snapshot) {
|
|
7205
|
+
ctx.activityIndicator.updateAgentRun(snapshot);
|
|
7206
|
+
ctx.runRegistryPoller.ensureRunning();
|
|
7207
|
+
} else {
|
|
7208
|
+
ctx.activityIndicator.clearAgentRun();
|
|
7209
|
+
}
|
|
7210
|
+
}
|
|
7211
|
+
}
|
|
7212
|
+
);
|
|
7213
|
+
const wasForceStopped = ctx.forcedStopEpoch === myEpoch;
|
|
7214
|
+
if (wasForceStopped) {
|
|
7215
|
+
if (runResult.dialogId || runResult.turnTokens) {
|
|
7216
|
+
const nextDialogKey = runResult.dialogId ? runResult.dialogId === ctx.state.dialogId && ctx.state.dialogKey ? ctx.state.dialogKey : ctx.state.dialogOwnerId ? `dialog-${ctx.state.dialogOwnerId}-${runResult.dialogId}` : void 0 : ctx.state.dialogKey;
|
|
7217
|
+
ctx.state = {
|
|
7218
|
+
...ctx.state,
|
|
7219
|
+
...runResult.dialogId ? {
|
|
7220
|
+
dialogId: runResult.dialogId,
|
|
7221
|
+
dialogKey: nextDialogKey,
|
|
7222
|
+
dialogLabel: runResult.title || runResult.dialogId,
|
|
7223
|
+
...runResult.title ? { dialogTitle: runResult.title } : {}
|
|
7224
|
+
} : {},
|
|
7225
|
+
...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {},
|
|
7226
|
+
...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {}
|
|
7227
|
+
};
|
|
7228
|
+
if (runResult.dialogId && nextDialogKey) {
|
|
7229
|
+
ctx.refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
|
|
7230
|
+
}
|
|
7231
|
+
}
|
|
7232
|
+
scheduleTitlePatchSync(runResult);
|
|
7233
|
+
return { ok: false, aborted: true };
|
|
7234
|
+
}
|
|
7235
|
+
const wasAborted = ctx.activeTurnAbort.signal.aborted;
|
|
7236
|
+
ctx.activeTurnAbort = null;
|
|
7237
|
+
if (shouldEmitTerminalBell({
|
|
7238
|
+
wasAborted,
|
|
7239
|
+
streamInterrupted: runResult.streamInterrupted,
|
|
7240
|
+
exitCode: runResult.exitCode,
|
|
7241
|
+
interactive: isInteractiveInput(ctx.input)
|
|
7242
|
+
})) {
|
|
7243
|
+
emitTerminalBell(ctx.output);
|
|
7244
|
+
}
|
|
7245
|
+
if (isInteractiveInput(ctx.input)) {
|
|
7246
|
+
finalizeCurrentTurn(ctx.history);
|
|
7247
|
+
ctx.flushPendingRender();
|
|
7248
|
+
ctx.renderHistoryToOutput();
|
|
7249
|
+
if (ctx.fixedInput.active) ctx.fixedInput.repaint(ctx.buffer, ctx.cursorPos);
|
|
7250
|
+
}
|
|
7251
|
+
if (wasAborted) {
|
|
7252
|
+
if (runResult.pendingToolName) {
|
|
7253
|
+
ctx.emitCommandOutput(t("turnStoppedToolPending", runResult.pendingToolName));
|
|
7254
|
+
} else {
|
|
7255
|
+
ctx.emitCommandOutput(t("turnStopped"));
|
|
7256
|
+
}
|
|
7257
|
+
}
|
|
7258
|
+
if (runResult.dialogId || runResult.turnTokens || runResult.contextWindow) {
|
|
7259
|
+
const nextDialogKey = runResult.dialogId ? runResult.dialogId === ctx.state.dialogId && ctx.state.dialogKey ? ctx.state.dialogKey : ctx.state.dialogOwnerId ? `dialog-${ctx.state.dialogOwnerId}-${runResult.dialogId}` : void 0 : ctx.state.dialogKey;
|
|
7260
|
+
ctx.state = {
|
|
7261
|
+
...ctx.state,
|
|
7262
|
+
...runResult.dialogId ? {
|
|
7263
|
+
dialogId: runResult.dialogId,
|
|
7264
|
+
dialogKey: nextDialogKey,
|
|
7265
|
+
dialogLabel: runResult.title || runResult.dialogId,
|
|
7266
|
+
...runResult.title ? { dialogTitle: runResult.title } : {}
|
|
7267
|
+
} : {},
|
|
7268
|
+
...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {},
|
|
7269
|
+
...runResult.contextWindow ? { contextWindow: runResult.contextWindow } : {},
|
|
7270
|
+
// input_tokens 是累计上下文输入(含历史消息),把它持久化到
|
|
7271
|
+
// estimatedContextTokens:下一轮若 provider 不返回 usage,context
|
|
7272
|
+
// chip 仍显示真实累计占用而不是回退到启动时的静态估算。
|
|
7273
|
+
...runResult.turnTokens && runResult.turnTokens.input > 0 ? { estimatedContextTokens: runResult.turnTokens.input } : {},
|
|
7274
|
+
...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {}
|
|
7275
|
+
};
|
|
7276
|
+
if (runResult.dialogId && nextDialogKey) {
|
|
7277
|
+
ctx.refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
|
|
7278
|
+
}
|
|
7279
|
+
}
|
|
7280
|
+
scheduleTitlePatchSync(runResult);
|
|
7281
|
+
if (!wasAborted && runResult.exitCode !== 0) {
|
|
7282
|
+
if (isBalanceExhaustedError(runResult.localError) && runResult.dialogId) {
|
|
7283
|
+
ctx.emitCommandOutput(t("balanceExhaustedHint"));
|
|
7284
|
+
} else if (isQuotaExhaustedError(runResult.localError)) {
|
|
7285
|
+
ctx.emitCommandOutput(t("quotaExhaustedHint"));
|
|
7286
|
+
} else if (runResult.dialogId) {
|
|
7287
|
+
ctx.emitCommandOutput(t("dialogPreservedHint"));
|
|
7288
|
+
} else {
|
|
7289
|
+
ctx.emitCommandOutput(t("dialogNotSavedHint"));
|
|
7290
|
+
}
|
|
7291
|
+
}
|
|
7292
|
+
return { ok: !wasAborted, aborted: wasAborted };
|
|
7293
|
+
} finally {
|
|
7294
|
+
ctx.activityIndicator.stop();
|
|
7295
|
+
ctx.activeTurnAbort = null;
|
|
7296
|
+
}
|
|
7297
|
+
}
|
|
7298
|
+
function ensureChatQueueBinding(ctx, actionGateHandler, confirmDestructiveAction) {
|
|
7299
|
+
if (ctx.chatQueueBinding) return ctx.chatQueueBinding;
|
|
7300
|
+
ctx.chatQueueBinding = createChatQueueTuiBinding(async (text) => {
|
|
7301
|
+
return runOneAgentTurn(ctx, text, [], actionGateHandler, confirmDestructiveAction);
|
|
7302
|
+
});
|
|
7303
|
+
return ctx.chatQueueBinding;
|
|
7304
|
+
}
|
|
7305
|
+
function preemptAndAbortForDrain(binding, activeTurnAbort) {
|
|
7306
|
+
if (binding.preemptForDrain() && activeTurnAbort) {
|
|
7307
|
+
activeTurnAbort.abort();
|
|
7308
|
+
}
|
|
7309
|
+
}
|
|
7079
7310
|
var latestWorkspaceThemeOwner = 0;
|
|
7080
7311
|
async function runTuiWorkspace(options) {
|
|
7081
7312
|
initCliLocale(options.env ?? process.env);
|
|
@@ -7355,220 +7586,6 @@ ${content}`);
|
|
|
7355
7586
|
});
|
|
7356
7587
|
};
|
|
7357
7588
|
let composerDecoderDrain = null;
|
|
7358
|
-
const runOneAgentTurn = async (inputMsg, imageUrls, actionGateHandler, confirmDestructiveAction) => {
|
|
7359
|
-
const scheduleTitlePatchSync = (runResult) => {
|
|
7360
|
-
if (!runResult.titlePatchPromise || !runResult.dialogId) return;
|
|
7361
|
-
const patchDialogId = runResult.dialogId;
|
|
7362
|
-
runResult.titlePatchPromise.then((patchedTitle) => {
|
|
7363
|
-
if (!patchedTitle || sessionEnded) return;
|
|
7364
|
-
if (state.dialogId !== patchDialogId) return;
|
|
7365
|
-
if (state.dialogLabel === patchedTitle) return;
|
|
7366
|
-
state = {
|
|
7367
|
-
...state,
|
|
7368
|
-
dialogLabel: patchedTitle,
|
|
7369
|
-
dialogTitle: patchedTitle
|
|
7370
|
-
};
|
|
7371
|
-
syncWindowTitle();
|
|
7372
|
-
}).catch(() => {
|
|
7373
|
-
});
|
|
7374
|
-
};
|
|
7375
|
-
let req = createTurnRequest(inputMsg);
|
|
7376
|
-
if (req.event.kind === "child-run-completed") {
|
|
7377
|
-
const nowMs = Date.now();
|
|
7378
|
-
const { remainingRuns, claimedRunIds } = filterUnclaimedChildRuns(
|
|
7379
|
-
req.event.runs,
|
|
7380
|
-
{ env: effectiveEnv, now: () => nowMs }
|
|
7381
|
-
);
|
|
7382
|
-
for (const runId of claimedRunIds) {
|
|
7383
|
-
runCompletionWatcher.markAcknowledged(runId);
|
|
7384
|
-
}
|
|
7385
|
-
if (remainingRuns.length === 0) {
|
|
7386
|
-
for (const r of req.event.runs) {
|
|
7387
|
-
runCompletionWatcher.markAcknowledged(r.runId);
|
|
7388
|
-
}
|
|
7389
|
-
return { ok: true, aborted: false };
|
|
7390
|
-
}
|
|
7391
|
-
for (const r of remainingRuns) {
|
|
7392
|
-
runCompletionWatcher.markAcknowledged(r.runId);
|
|
7393
|
-
}
|
|
7394
|
-
if (remainingRuns.length !== req.event.runs.length) {
|
|
7395
|
-
const text = buildWakeMessage(remainingRuns, nowMs);
|
|
7396
|
-
const displayText = buildWakeDisplayText(remainingRuns, nowMs);
|
|
7397
|
-
const updatedEvent = {
|
|
7398
|
-
kind: "child-run-completed",
|
|
7399
|
-
runs: remainingRuns,
|
|
7400
|
-
text,
|
|
7401
|
-
displayText
|
|
7402
|
-
};
|
|
7403
|
-
req = {
|
|
7404
|
-
text,
|
|
7405
|
-
event: updatedEvent
|
|
7406
|
-
};
|
|
7407
|
-
}
|
|
7408
|
-
}
|
|
7409
|
-
const message = req.text;
|
|
7410
|
-
forcedStop = false;
|
|
7411
|
-
turnEpoch += 1;
|
|
7412
|
-
const myEpoch = turnEpoch;
|
|
7413
|
-
history.followBottom = true;
|
|
7414
|
-
const isInternalEvent = req.event.kind !== "user";
|
|
7415
|
-
const transcriptText = req.event.kind === "child-run-completed" ? req.event.displayText ?? req.event.text : message;
|
|
7416
|
-
startTurn(history, isInternalEvent ? "assistant" : "user");
|
|
7417
|
-
appendToCurrentTurn(
|
|
7418
|
-
history,
|
|
7419
|
-
isInternalEvent ? dimCliText(transcriptText, resolveCliColorEnabled()) : transcriptText
|
|
7420
|
-
);
|
|
7421
|
-
finalizeCurrentTurn(history);
|
|
7422
|
-
renderHistoryToOutput();
|
|
7423
|
-
if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
|
|
7424
|
-
startTurn(history, "assistant");
|
|
7425
|
-
const agentOutput = isInteractiveInput(input) ? createHistoryOutputStream(history, () => {
|
|
7426
|
-
scheduleRender();
|
|
7427
|
-
}) : output;
|
|
7428
|
-
const requestUserChoice = isInteractiveInput(input) && dialogHost ? async (req2) => {
|
|
7429
|
-
modalOwnsKeyboard = true;
|
|
7430
|
-
try {
|
|
7431
|
-
return await dialogHost.run(
|
|
7432
|
-
(anchor) => runAskChoiceDialog({
|
|
7433
|
-
request: req2,
|
|
7434
|
-
input,
|
|
7435
|
-
output,
|
|
7436
|
-
...anchor
|
|
7437
|
-
})
|
|
7438
|
-
);
|
|
7439
|
-
} catch {
|
|
7440
|
-
return { kind: "cancelled" };
|
|
7441
|
-
} finally {
|
|
7442
|
-
composerDecoderDrain?.();
|
|
7443
|
-
modalOwnsKeyboard = false;
|
|
7444
|
-
}
|
|
7445
|
-
} : void 0;
|
|
7446
|
-
try {
|
|
7447
|
-
activeTurnAbort = new AbortController();
|
|
7448
|
-
activeTurnEpoch = myEpoch;
|
|
7449
|
-
const runResult = await runAgentChat(
|
|
7450
|
-
options.scriptDir,
|
|
7451
|
-
state,
|
|
7452
|
-
message,
|
|
7453
|
-
options.env ?? process.env,
|
|
7454
|
-
agentOutput,
|
|
7455
|
-
options.agentRunner,
|
|
7456
|
-
{
|
|
7457
|
-
...imageUrls.length > 0 ? { imageUrls } : {},
|
|
7458
|
-
actionGateHandler,
|
|
7459
|
-
...confirmDestructiveAction ? { confirmDestructiveAction } : {},
|
|
7460
|
-
...requestUserChoice ? { requestUserChoice } : {},
|
|
7461
|
-
abortSignal: activeTurnAbort.signal,
|
|
7462
|
-
pastedTextStore: pasteStore,
|
|
7463
|
-
activityReporter,
|
|
7464
|
-
onAgentRunStatus: (snapshot) => {
|
|
7465
|
-
if (snapshot) {
|
|
7466
|
-
activityIndicator.updateAgentRun(snapshot);
|
|
7467
|
-
runRegistryPoller.ensureRunning();
|
|
7468
|
-
} else {
|
|
7469
|
-
activityIndicator.clearAgentRun();
|
|
7470
|
-
}
|
|
7471
|
-
}
|
|
7472
|
-
}
|
|
7473
|
-
);
|
|
7474
|
-
const wasForceStopped = forcedStopEpoch === myEpoch;
|
|
7475
|
-
if (wasForceStopped) {
|
|
7476
|
-
if (runResult.dialogId || runResult.turnTokens) {
|
|
7477
|
-
const nextDialogKey = runResult.dialogId ? runResult.dialogId === state.dialogId && state.dialogKey ? state.dialogKey : state.dialogOwnerId ? `dialog-${state.dialogOwnerId}-${runResult.dialogId}` : void 0 : state.dialogKey;
|
|
7478
|
-
state = {
|
|
7479
|
-
...state,
|
|
7480
|
-
...runResult.dialogId ? {
|
|
7481
|
-
dialogId: runResult.dialogId,
|
|
7482
|
-
dialogKey: nextDialogKey,
|
|
7483
|
-
dialogLabel: runResult.title || runResult.dialogId,
|
|
7484
|
-
...runResult.title ? { dialogTitle: runResult.title } : {}
|
|
7485
|
-
} : {},
|
|
7486
|
-
...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {},
|
|
7487
|
-
...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {}
|
|
7488
|
-
};
|
|
7489
|
-
if (runResult.dialogId && nextDialogKey) {
|
|
7490
|
-
refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
|
|
7491
|
-
}
|
|
7492
|
-
}
|
|
7493
|
-
scheduleTitlePatchSync(runResult);
|
|
7494
|
-
return { ok: false, aborted: true };
|
|
7495
|
-
}
|
|
7496
|
-
const wasAborted = activeTurnAbort.signal.aborted;
|
|
7497
|
-
activeTurnAbort = null;
|
|
7498
|
-
if (shouldEmitTerminalBell({
|
|
7499
|
-
wasAborted,
|
|
7500
|
-
streamInterrupted: runResult.streamInterrupted,
|
|
7501
|
-
exitCode: runResult.exitCode,
|
|
7502
|
-
interactive: isInteractiveInput(input)
|
|
7503
|
-
})) {
|
|
7504
|
-
emitTerminalBell(output);
|
|
7505
|
-
}
|
|
7506
|
-
if (isInteractiveInput(input)) {
|
|
7507
|
-
finalizeCurrentTurn(history);
|
|
7508
|
-
flushPendingRender();
|
|
7509
|
-
renderHistoryToOutput();
|
|
7510
|
-
if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
|
|
7511
|
-
}
|
|
7512
|
-
if (wasAborted) {
|
|
7513
|
-
if (runResult.pendingToolName) {
|
|
7514
|
-
emitCommandOutput(t("turnStoppedToolPending", runResult.pendingToolName));
|
|
7515
|
-
} else {
|
|
7516
|
-
emitCommandOutput(t("turnStopped"));
|
|
7517
|
-
}
|
|
7518
|
-
}
|
|
7519
|
-
if (runResult.dialogId || runResult.turnTokens || runResult.contextWindow) {
|
|
7520
|
-
const nextDialogKey = runResult.dialogId ? runResult.dialogId === state.dialogId && state.dialogKey ? state.dialogKey : state.dialogOwnerId ? `dialog-${state.dialogOwnerId}-${runResult.dialogId}` : void 0 : state.dialogKey;
|
|
7521
|
-
state = {
|
|
7522
|
-
...state,
|
|
7523
|
-
...runResult.dialogId ? {
|
|
7524
|
-
dialogId: runResult.dialogId,
|
|
7525
|
-
dialogKey: nextDialogKey,
|
|
7526
|
-
dialogLabel: runResult.title || runResult.dialogId,
|
|
7527
|
-
...runResult.title ? { dialogTitle: runResult.title } : {}
|
|
7528
|
-
} : {},
|
|
7529
|
-
...runResult.turnTokens ? { turnTokens: runResult.turnTokens } : {},
|
|
7530
|
-
...runResult.contextWindow ? { contextWindow: runResult.contextWindow } : {},
|
|
7531
|
-
// input_tokens 是累计上下文输入(含历史消息),把它持久化到
|
|
7532
|
-
// estimatedContextTokens:下一轮若 provider 不返回 usage,context
|
|
7533
|
-
// chip 仍显示真实累计占用而不是回退到启动时的静态估算。
|
|
7534
|
-
...runResult.turnTokens && runResult.turnTokens.input > 0 ? { estimatedContextTokens: runResult.turnTokens.input } : {},
|
|
7535
|
-
...runResult.cachedMemoryOverlay !== void 0 ? { cachedMemoryOverlay: runResult.cachedMemoryOverlay } : {}
|
|
7536
|
-
};
|
|
7537
|
-
if (runResult.dialogId && nextDialogKey) {
|
|
7538
|
-
refreshDialogTotalCredits(runResult.dialogId, nextDialogKey);
|
|
7539
|
-
}
|
|
7540
|
-
}
|
|
7541
|
-
scheduleTitlePatchSync(runResult);
|
|
7542
|
-
if (!wasAborted && runResult.exitCode !== 0) {
|
|
7543
|
-
if (isBalanceExhaustedError(runResult.localError) && runResult.dialogId) {
|
|
7544
|
-
emitCommandOutput(t("balanceExhaustedHint"));
|
|
7545
|
-
} else if (isQuotaExhaustedError(runResult.localError)) {
|
|
7546
|
-
emitCommandOutput(t("quotaExhaustedHint"));
|
|
7547
|
-
} else if (runResult.dialogId) {
|
|
7548
|
-
emitCommandOutput(t("dialogPreservedHint"));
|
|
7549
|
-
} else {
|
|
7550
|
-
emitCommandOutput(t("dialogNotSavedHint"));
|
|
7551
|
-
}
|
|
7552
|
-
}
|
|
7553
|
-
return { ok: !wasAborted, aborted: wasAborted };
|
|
7554
|
-
} finally {
|
|
7555
|
-
activityIndicator.stop();
|
|
7556
|
-
activeTurnAbort = null;
|
|
7557
|
-
}
|
|
7558
|
-
};
|
|
7559
|
-
let chatQueueBinding = null;
|
|
7560
|
-
const ensureChatQueueBinding = (actionGateHandler, confirmDestructiveAction) => {
|
|
7561
|
-
if (chatQueueBinding) return chatQueueBinding;
|
|
7562
|
-
chatQueueBinding = createChatQueueTuiBinding(async (text) => {
|
|
7563
|
-
return runOneAgentTurn(text, [], actionGateHandler, confirmDestructiveAction);
|
|
7564
|
-
});
|
|
7565
|
-
return chatQueueBinding;
|
|
7566
|
-
};
|
|
7567
|
-
const preemptAndAbortForDrain = (binding) => {
|
|
7568
|
-
if (binding.preemptForDrain() && activeTurnAbort) {
|
|
7569
|
-
activeTurnAbort.abort();
|
|
7570
|
-
}
|
|
7571
|
-
};
|
|
7572
7589
|
const emitCommandOutput = (text, command = "") => {
|
|
7573
7590
|
if (!text) return;
|
|
7574
7591
|
if (!isInteractiveInput(input)) {
|
|
@@ -7581,6 +7598,92 @@ ${content}`);
|
|
|
7581
7598
|
renderHistoryToOutput();
|
|
7582
7599
|
if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
|
|
7583
7600
|
};
|
|
7601
|
+
let chatQueueBinding = null;
|
|
7602
|
+
const turnCtx = {
|
|
7603
|
+
get state() {
|
|
7604
|
+
return state;
|
|
7605
|
+
},
|
|
7606
|
+
set state(v) {
|
|
7607
|
+
state = v;
|
|
7608
|
+
},
|
|
7609
|
+
get forcedStop() {
|
|
7610
|
+
return forcedStop;
|
|
7611
|
+
},
|
|
7612
|
+
set forcedStop(v) {
|
|
7613
|
+
forcedStop = v;
|
|
7614
|
+
},
|
|
7615
|
+
get forcedStopEpoch() {
|
|
7616
|
+
return forcedStopEpoch;
|
|
7617
|
+
},
|
|
7618
|
+
set forcedStopEpoch(v) {
|
|
7619
|
+
forcedStopEpoch = v;
|
|
7620
|
+
},
|
|
7621
|
+
get turnEpoch() {
|
|
7622
|
+
return turnEpoch;
|
|
7623
|
+
},
|
|
7624
|
+
set turnEpoch(v) {
|
|
7625
|
+
turnEpoch = v;
|
|
7626
|
+
},
|
|
7627
|
+
get activeTurnAbort() {
|
|
7628
|
+
return activeTurnAbort;
|
|
7629
|
+
},
|
|
7630
|
+
set activeTurnAbort(v) {
|
|
7631
|
+
activeTurnAbort = v;
|
|
7632
|
+
},
|
|
7633
|
+
get activeTurnEpoch() {
|
|
7634
|
+
return activeTurnEpoch;
|
|
7635
|
+
},
|
|
7636
|
+
set activeTurnEpoch(v) {
|
|
7637
|
+
activeTurnEpoch = v;
|
|
7638
|
+
},
|
|
7639
|
+
get modalOwnsKeyboard() {
|
|
7640
|
+
return modalOwnsKeyboard;
|
|
7641
|
+
},
|
|
7642
|
+
set modalOwnsKeyboard(v) {
|
|
7643
|
+
modalOwnsKeyboard = v;
|
|
7644
|
+
},
|
|
7645
|
+
get composerDecoderDrain() {
|
|
7646
|
+
return composerDecoderDrain;
|
|
7647
|
+
},
|
|
7648
|
+
set composerDecoderDrain(v) {
|
|
7649
|
+
composerDecoderDrain = v;
|
|
7650
|
+
},
|
|
7651
|
+
get chatQueueBinding() {
|
|
7652
|
+
return chatQueueBinding;
|
|
7653
|
+
},
|
|
7654
|
+
set chatQueueBinding(v) {
|
|
7655
|
+
chatQueueBinding = v;
|
|
7656
|
+
},
|
|
7657
|
+
get sessionEnded() {
|
|
7658
|
+
return sessionEnded;
|
|
7659
|
+
},
|
|
7660
|
+
get buffer() {
|
|
7661
|
+
return buffer;
|
|
7662
|
+
},
|
|
7663
|
+
get cursorPos() {
|
|
7664
|
+
return cursorPos;
|
|
7665
|
+
},
|
|
7666
|
+
get fixedInput() {
|
|
7667
|
+
return fixedInput;
|
|
7668
|
+
},
|
|
7669
|
+
options,
|
|
7670
|
+
effectiveEnv,
|
|
7671
|
+
history,
|
|
7672
|
+
activityIndicator,
|
|
7673
|
+
activityReporter,
|
|
7674
|
+
runRegistryPoller,
|
|
7675
|
+
runCompletionWatcher,
|
|
7676
|
+
pasteStore,
|
|
7677
|
+
dialogHost,
|
|
7678
|
+
input,
|
|
7679
|
+
output,
|
|
7680
|
+
syncWindowTitle,
|
|
7681
|
+
renderHistoryToOutput,
|
|
7682
|
+
scheduleRender,
|
|
7683
|
+
flushPendingRender,
|
|
7684
|
+
refreshDialogTotalCredits,
|
|
7685
|
+
emitCommandOutput
|
|
7686
|
+
};
|
|
7584
7687
|
const persistExplicitAgentSwitch = (previousAgentKey) => {
|
|
7585
7688
|
if (state.agentKey === previousAgentKey) return false;
|
|
7586
7689
|
persistAgentSelection(
|
|
@@ -7596,8 +7699,9 @@ ${content}`);
|
|
|
7596
7699
|
const previousAgentKey = state.agentKey;
|
|
7597
7700
|
state = result.nextState;
|
|
7598
7701
|
const interactive = isInteractiveInput(input);
|
|
7599
|
-
if (result.action?.type !== "
|
|
7600
|
-
|
|
7702
|
+
if (result.action?.type !== "exit" && result.output) {
|
|
7703
|
+
const shouldEmit = result.action?.type !== "chat" || !interactive;
|
|
7704
|
+
if (shouldEmit) emitCommandOutput(result.output, interactive ? line.trim() : "");
|
|
7601
7705
|
}
|
|
7602
7706
|
if (state.agentKey !== previousAgentKey) {
|
|
7603
7707
|
persistExplicitAgentSwitch(previousAgentKey);
|
|
@@ -7990,27 +8094,22 @@ ${stderrText.trim()}
|
|
|
7990
8094
|
}
|
|
7991
8095
|
}
|
|
7992
8096
|
if (result.action?.type === "chat") {
|
|
7993
|
-
const
|
|
7994
|
-
|
|
7995
|
-
|
|
7996
|
-
|
|
7997
|
-
let imageUrls = [];
|
|
7998
|
-
if (pathsToRead.length > 0) {
|
|
7999
|
-
const readResult = await readImagePaths(pathsToRead, {
|
|
8000
|
-
onFailure: (_path, err) => output.write(`[nolo] image skipped: ${err.message}
|
|
8097
|
+
const { imageUrls } = await resolveAttachmentImageUrls({
|
|
8098
|
+
actionImagePaths: result.action.imagePaths,
|
|
8099
|
+
attachedImages: state.attachedImages,
|
|
8100
|
+
onFailure: (_path, err) => output.write(`[nolo] image skipped: ${err.message}
|
|
8001
8101
|
`)
|
|
8002
|
-
|
|
8003
|
-
imageUrls = readResult.images.map((img) => img.dataUrl);
|
|
8004
|
-
}
|
|
8102
|
+
});
|
|
8005
8103
|
state = {
|
|
8006
8104
|
...state,
|
|
8007
8105
|
attachedImages: []
|
|
8008
8106
|
};
|
|
8009
8107
|
history.followBottom = true;
|
|
8010
|
-
const binding = ensureChatQueueBinding(actionGateHandler, confirmDestructiveAction);
|
|
8108
|
+
const binding = ensureChatQueueBinding(turnCtx, actionGateHandler, confirmDestructiveAction);
|
|
8011
8109
|
binding.notifyTurnStart();
|
|
8012
8110
|
try {
|
|
8013
8111
|
const outcome = await runOneAgentTurn(
|
|
8112
|
+
turnCtx,
|
|
8014
8113
|
result.action.message,
|
|
8015
8114
|
imageUrls,
|
|
8016
8115
|
actionGateHandler,
|
|
@@ -8169,12 +8268,13 @@ ${err.message}` : ""}`
|
|
|
8169
8268
|
const runIdleTextTurn = async (inputMsg) => {
|
|
8170
8269
|
const req = createTurnRequest(inputMsg);
|
|
8171
8270
|
const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
|
|
8172
|
-
const binding = ensureChatQueueBinding(actionGateHandler, confirmDestructiveAction);
|
|
8271
|
+
const binding = ensureChatQueueBinding(turnCtx, actionGateHandler, confirmDestructiveAction);
|
|
8173
8272
|
binding.notifyTurnStart();
|
|
8174
8273
|
busy = true;
|
|
8175
8274
|
fixedInput.enterOutputMode(req.text);
|
|
8176
8275
|
try {
|
|
8177
8276
|
const outcome = await runOneAgentTurn(
|
|
8277
|
+
turnCtx,
|
|
8178
8278
|
req,
|
|
8179
8279
|
[],
|
|
8180
8280
|
actionGateHandler,
|
|
@@ -8198,7 +8298,7 @@ ${err.message}` : ""}`
|
|
|
8198
8298
|
if (done) return;
|
|
8199
8299
|
if (busy || fixedInput.isPaused()) {
|
|
8200
8300
|
const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
|
|
8201
|
-
ensureChatQueueBinding(actionGateHandler, confirmDestructiveAction).enqueue(event);
|
|
8301
|
+
ensureChatQueueBinding(turnCtx, actionGateHandler, confirmDestructiveAction).enqueue(event);
|
|
8202
8302
|
if (fixedInput.active && !fixedInput.isPaused()) {
|
|
8203
8303
|
fixedInput.repaint(buffer, cursorPos);
|
|
8204
8304
|
}
|
|
@@ -8258,6 +8358,109 @@ ${err.message}` : ""}`
|
|
|
8258
8358
|
paintFrame(buffer);
|
|
8259
8359
|
}, 60);
|
|
8260
8360
|
};
|
|
8361
|
+
const handleCtrlCKey = async (busyLock) => {
|
|
8362
|
+
if (busyLock && activeTurnAbort) {
|
|
8363
|
+
const stopBinding = chatQueueBinding;
|
|
8364
|
+
if (stopBinding && stopBinding.queueLength() > 0) {
|
|
8365
|
+
stopBinding.preemptForStop();
|
|
8366
|
+
}
|
|
8367
|
+
activityIndicator.markStopping();
|
|
8368
|
+
activeTurnAbort.abort();
|
|
8369
|
+
return;
|
|
8370
|
+
}
|
|
8371
|
+
const now = Date.now();
|
|
8372
|
+
const hasSelection = selectionState.anchor !== null && selectionState.head !== null && !areSelectionPointsEqual(selectionState.anchor, selectionState.head);
|
|
8373
|
+
if (hasSelection) {
|
|
8374
|
+
const tty = output;
|
|
8375
|
+
const columns = tty.columns ?? 80;
|
|
8376
|
+
const contentWidth = Math.max(1, columns - 1);
|
|
8377
|
+
const textToCopy = extractSelectedText(
|
|
8378
|
+
history,
|
|
8379
|
+
selectionState.anchor,
|
|
8380
|
+
selectionState.head,
|
|
8381
|
+
contentWidth
|
|
8382
|
+
);
|
|
8383
|
+
clearSelection();
|
|
8384
|
+
if (textToCopy.length > 0) {
|
|
8385
|
+
try {
|
|
8386
|
+
await writeClipboard2(textToCopy);
|
|
8387
|
+
emitCommandOutput(t("copiedSelection"));
|
|
8388
|
+
} catch (error) {
|
|
8389
|
+
emitCommandOutput(
|
|
8390
|
+
`[nolo] ${t("copyFailed")}: ${toErrorMessage(error)}`
|
|
8391
|
+
);
|
|
8392
|
+
}
|
|
8393
|
+
}
|
|
8394
|
+
paintFrame(buffer);
|
|
8395
|
+
return;
|
|
8396
|
+
}
|
|
8397
|
+
if (selectionState.anchor) {
|
|
8398
|
+
clearSelection();
|
|
8399
|
+
}
|
|
8400
|
+
if (buffer.length > 0) {
|
|
8401
|
+
buffer = "";
|
|
8402
|
+
cursorPos = 0;
|
|
8403
|
+
if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
|
|
8404
|
+
emitCommandOutput(t("ctrlCClearedDraft"));
|
|
8405
|
+
return;
|
|
8406
|
+
}
|
|
8407
|
+
if (lastCtrlCDoublePress !== null && now - lastCtrlCDoublePress <= 1e3) {
|
|
8408
|
+
lastCtrlCDoublePress = null;
|
|
8409
|
+
fixedInput.disable();
|
|
8410
|
+
finish();
|
|
8411
|
+
return;
|
|
8412
|
+
}
|
|
8413
|
+
lastCtrlCDoublePress = now;
|
|
8414
|
+
emitCommandOutput(t("ctrlCExitHint"));
|
|
8415
|
+
};
|
|
8416
|
+
const handleBusyLocalSlash = async (submittedText, busySlashCommand) => {
|
|
8417
|
+
releaseCollapsedPasteReferences(submittedText, pasteStore);
|
|
8418
|
+
buffer = "";
|
|
8419
|
+
cursorPos = 0;
|
|
8420
|
+
if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
|
|
8421
|
+
const beforeAgentKey = state.agentKey;
|
|
8422
|
+
const res = handleTuiInput(submittedText, state);
|
|
8423
|
+
if (res.action?.type === "theme-refresh") {
|
|
8424
|
+
state = res.nextState;
|
|
8425
|
+
const detected = await detectTerminalBackground({
|
|
8426
|
+
stdin: input,
|
|
8427
|
+
stdout: output,
|
|
8428
|
+
allowSystemFallback: true
|
|
8429
|
+
});
|
|
8430
|
+
let refreshMsg = "";
|
|
8431
|
+
if (detected && applyDetectedBackground(detected)) {
|
|
8432
|
+
refreshMsg = t("themeRefreshed", detected.brightness);
|
|
8433
|
+
} else if (detected) {
|
|
8434
|
+
refreshMsg = t("themeRefreshed", detected.brightness);
|
|
8435
|
+
} else {
|
|
8436
|
+
refreshMsg = t("themeRefreshFailed");
|
|
8437
|
+
}
|
|
8438
|
+
if (refreshMsg) {
|
|
8439
|
+
output.write(`${refreshMsg}
|
|
8440
|
+
`);
|
|
8441
|
+
}
|
|
8442
|
+
} else if (res.action) {
|
|
8443
|
+
output.write(
|
|
8444
|
+
"Model picker isn't available while a reply is running. Use `/switch <name>` to switch now (takes effect on the next turn), or wait for the reply to finish.\n"
|
|
8445
|
+
);
|
|
8446
|
+
} else {
|
|
8447
|
+
state = res.nextState;
|
|
8448
|
+
let msg = res.output;
|
|
8449
|
+
if (busySlashCommand === "/switch" && persistExplicitAgentSwitch(beforeAgentKey)) {
|
|
8450
|
+
const hint = "Note: the new model takes effect on the next turn. Switching models may consume more tokens because the conversation context is re-sent to the new model.";
|
|
8451
|
+
msg = msg ? `${msg}
|
|
8452
|
+
${hint}` : hint;
|
|
8453
|
+
}
|
|
8454
|
+
if (msg) {
|
|
8455
|
+
output.write(`${msg}
|
|
8456
|
+
`);
|
|
8457
|
+
}
|
|
8458
|
+
}
|
|
8459
|
+
if (busySlashCommand === "/theme") {
|
|
8460
|
+
renderHistoryToOutput();
|
|
8461
|
+
if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
|
|
8462
|
+
}
|
|
8463
|
+
};
|
|
8261
8464
|
const handleInputToken = async (sequence) => {
|
|
8262
8465
|
if (done) return;
|
|
8263
8466
|
if (modalOwnsKeyboard) {
|
|
@@ -8280,7 +8483,7 @@ ${merged}` : merged;
|
|
|
8280
8483
|
t(busy ? "flushQueuedBusyHint" : "flushQueuedIdleHint", String(totalCount))
|
|
8281
8484
|
);
|
|
8282
8485
|
if (busy) {
|
|
8283
|
-
preemptAndAbortForDrain(chatQueueBinding);
|
|
8486
|
+
preemptAndAbortForDrain(chatQueueBinding, activeTurnAbort);
|
|
8284
8487
|
return;
|
|
8285
8488
|
}
|
|
8286
8489
|
buffer = "";
|
|
@@ -8387,59 +8590,7 @@ ${merged}` : merged;
|
|
|
8387
8590
|
return;
|
|
8388
8591
|
}
|
|
8389
8592
|
if (sequence === "") {
|
|
8390
|
-
|
|
8391
|
-
const stopBinding = chatQueueBinding;
|
|
8392
|
-
if (stopBinding && stopBinding.queueLength() > 0) {
|
|
8393
|
-
stopBinding.preemptForStop();
|
|
8394
|
-
}
|
|
8395
|
-
activityIndicator.markStopping();
|
|
8396
|
-
activeTurnAbort.abort();
|
|
8397
|
-
return;
|
|
8398
|
-
}
|
|
8399
|
-
const now = Date.now();
|
|
8400
|
-
const hasSelection = selectionState.anchor !== null && selectionState.head !== null && !areSelectionPointsEqual(selectionState.anchor, selectionState.head);
|
|
8401
|
-
if (hasSelection) {
|
|
8402
|
-
const tty = output;
|
|
8403
|
-
const columns = tty.columns ?? 80;
|
|
8404
|
-
const contentWidth = Math.max(1, columns - 1);
|
|
8405
|
-
const textToCopy = extractSelectedText(
|
|
8406
|
-
history,
|
|
8407
|
-
selectionState.anchor,
|
|
8408
|
-
selectionState.head,
|
|
8409
|
-
contentWidth
|
|
8410
|
-
);
|
|
8411
|
-
clearSelection();
|
|
8412
|
-
if (textToCopy.length > 0) {
|
|
8413
|
-
try {
|
|
8414
|
-
await writeClipboard2(textToCopy);
|
|
8415
|
-
emitCommandOutput(t("copiedSelection"));
|
|
8416
|
-
} catch (error) {
|
|
8417
|
-
emitCommandOutput(
|
|
8418
|
-
`[nolo] ${t("copyFailed")}: ${toErrorMessage(error)}`
|
|
8419
|
-
);
|
|
8420
|
-
}
|
|
8421
|
-
}
|
|
8422
|
-
paintFrame(buffer);
|
|
8423
|
-
return;
|
|
8424
|
-
}
|
|
8425
|
-
if (selectionState.anchor) {
|
|
8426
|
-
clearSelection();
|
|
8427
|
-
}
|
|
8428
|
-
if (buffer.length > 0) {
|
|
8429
|
-
buffer = "";
|
|
8430
|
-
cursorPos = 0;
|
|
8431
|
-
if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
|
|
8432
|
-
emitCommandOutput(t("ctrlCClearedDraft"));
|
|
8433
|
-
return;
|
|
8434
|
-
}
|
|
8435
|
-
if (lastCtrlCDoublePress !== null && now - lastCtrlCDoublePress <= 1e3) {
|
|
8436
|
-
lastCtrlCDoublePress = null;
|
|
8437
|
-
fixedInput.disable();
|
|
8438
|
-
finish();
|
|
8439
|
-
return;
|
|
8440
|
-
}
|
|
8441
|
-
lastCtrlCDoublePress = now;
|
|
8442
|
-
emitCommandOutput(t("ctrlCExitHint"));
|
|
8593
|
+
await handleCtrlCKey(busyLock);
|
|
8443
8594
|
return;
|
|
8444
8595
|
}
|
|
8445
8596
|
if (selectionState.anchor) {
|
|
@@ -8522,56 +8673,11 @@ ${merged}` : merged;
|
|
|
8522
8673
|
const busySlashCommand = trimmedText.split(/\s+/)[0]?.toLowerCase();
|
|
8523
8674
|
const isBusyLocalSlash = busySlashCommand === "/context" || busySlashCommand === "/ctx" || busySlashCommand === "/switch" || busySlashCommand === "/theme" || busySlashCommand === "/density" || busySlashCommand === "/runtime" || busySlashCommand === "/tools" || busySlashCommand === "/thinking" || busySlashCommand === "/auto" || busySlashCommand === "/tasks" || busySlashCommand === "/jobs" || busySlashCommand === "/procs" || busySlashCommand === "/agents" || busySlashCommand === "/doc" || busySlashCommand === "/skill" || busySlashCommand === "/customize" || busySlashCommand === "/login" || busySlashCommand === "/profile" || busySlashCommand === "/version";
|
|
8524
8675
|
if (isBusyLocalSlash) {
|
|
8525
|
-
|
|
8526
|
-
buffer = "";
|
|
8527
|
-
cursorPos = 0;
|
|
8528
|
-
if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
|
|
8529
|
-
const beforeAgentKey = state.agentKey;
|
|
8530
|
-
const res = handleTuiInput(submittedText, state);
|
|
8531
|
-
if (res.action?.type === "theme-refresh") {
|
|
8532
|
-
state = res.nextState;
|
|
8533
|
-
const detected = await detectTerminalBackground({
|
|
8534
|
-
stdin: input,
|
|
8535
|
-
stdout: output,
|
|
8536
|
-
allowSystemFallback: true
|
|
8537
|
-
});
|
|
8538
|
-
let refreshMsg = "";
|
|
8539
|
-
if (detected && applyDetectedBackground(detected)) {
|
|
8540
|
-
refreshMsg = t("themeRefreshed", detected.brightness);
|
|
8541
|
-
} else if (detected) {
|
|
8542
|
-
refreshMsg = t("themeRefreshed", detected.brightness);
|
|
8543
|
-
} else {
|
|
8544
|
-
refreshMsg = t("themeRefreshFailed");
|
|
8545
|
-
}
|
|
8546
|
-
if (refreshMsg) {
|
|
8547
|
-
output.write(`${refreshMsg}
|
|
8548
|
-
`);
|
|
8549
|
-
}
|
|
8550
|
-
} else if (res.action) {
|
|
8551
|
-
output.write(
|
|
8552
|
-
"Model picker isn't available while a reply is running. Use `/switch <name>` to switch now (takes effect on the next turn), or wait for the reply to finish.\n"
|
|
8553
|
-
);
|
|
8554
|
-
} else {
|
|
8555
|
-
state = res.nextState;
|
|
8556
|
-
let msg = res.output;
|
|
8557
|
-
if (busySlashCommand === "/switch" && persistExplicitAgentSwitch(beforeAgentKey)) {
|
|
8558
|
-
const hint = "Note: the new model takes effect on the next turn. Switching models may consume more tokens because the conversation context is re-sent to the new model.";
|
|
8559
|
-
msg = msg ? `${msg}
|
|
8560
|
-
${hint}` : hint;
|
|
8561
|
-
}
|
|
8562
|
-
if (msg) {
|
|
8563
|
-
output.write(`${msg}
|
|
8564
|
-
`);
|
|
8565
|
-
}
|
|
8566
|
-
}
|
|
8567
|
-
if (busySlashCommand === "/theme") {
|
|
8568
|
-
renderHistoryToOutput();
|
|
8569
|
-
if (fixedInput.active) fixedInput.repaint(buffer, cursorPos);
|
|
8570
|
-
}
|
|
8676
|
+
await handleBusyLocalSlash(submittedText, busySlashCommand);
|
|
8571
8677
|
return;
|
|
8572
8678
|
}
|
|
8573
8679
|
const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
|
|
8574
|
-
const binding = ensureChatQueueBinding(actionGateHandler, confirmDestructiveAction);
|
|
8680
|
+
const binding = ensureChatQueueBinding(turnCtx, actionGateHandler, confirmDestructiveAction);
|
|
8575
8681
|
const decision = binding.resolveSubmit({
|
|
8576
8682
|
text: submittedText,
|
|
8577
8683
|
isRunning: true
|
|
@@ -8583,13 +8689,13 @@ ${hint}` : hint;
|
|
|
8583
8689
|
fixedInput.repaint(buffer, cursorPos);
|
|
8584
8690
|
} else if (decision.kind === "queue-blocked") {
|
|
8585
8691
|
} else if (decision.kind === "noop" && !submittedText.trim() && binding.queueLength() > 0) {
|
|
8586
|
-
preemptAndAbortForDrain(binding);
|
|
8692
|
+
preemptAndAbortForDrain(binding, activeTurnAbort);
|
|
8587
8693
|
}
|
|
8588
8694
|
return;
|
|
8589
8695
|
}
|
|
8590
8696
|
if (!submittedText.trim() && chatQueueBinding && chatQueueBinding.queueLength() > 0) {
|
|
8591
8697
|
const { actionGateHandler, confirmDestructiveAction } = buildInteractiveTurnHandlers();
|
|
8592
|
-
const manualBinding = ensureChatQueueBinding(actionGateHandler, confirmDestructiveAction);
|
|
8698
|
+
const manualBinding = ensureChatQueueBinding(turnCtx, actionGateHandler, confirmDestructiveAction);
|
|
8593
8699
|
const drainedText = manualBinding.drainHeadForManualTurn();
|
|
8594
8700
|
if (drainedText !== null) {
|
|
8595
8701
|
buffer = "";
|