open-agents-ai 0.28.0 → 0.30.0
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/index.js +285 -112
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -10413,32 +10413,31 @@ Rules:
|
|
|
10413
10413
|
* Build a self-eval prompt for the agent when approaching timeout.
|
|
10414
10414
|
* Returns the prompt to inject. The agent will respond with a plan.
|
|
10415
10415
|
*/
|
|
10416
|
-
|
|
10416
|
+
buildHealthCheckPrompt(elapsedMs2, toolCallCount, repetitionScore, checkNumber) {
|
|
10417
10417
|
const elapsedMin = (elapsedMs2 / 6e4).toFixed(1);
|
|
10418
|
-
const remainingMin = (remainingMs / 6e4).toFixed(1);
|
|
10419
10418
|
const stuckWarning = repetitionScore > 0.5 ? `
|
|
10420
10419
|
\u26A0 REPETITION DETECTED: Your recent tool calls are ${Math.round(repetitionScore * 100)}% repetitive. You may be stuck in a loop.` : "";
|
|
10421
|
-
return `[
|
|
10420
|
+
return `[HEALTH CHECK #${checkNumber} \u2014 Progress Assessment]
|
|
10422
10421
|
|
|
10423
|
-
You have been working for ${elapsedMin} minutes with ${toolCallCount} tool calls.
|
|
10422
|
+
You have been working for ${elapsedMin} minutes with ${toolCallCount} tool calls. There is no time limit \u2014 take as long as you need.${stuckWarning}
|
|
10424
10423
|
|
|
10425
|
-
|
|
10424
|
+
Briefly assess your situation and choose ONE action:
|
|
10426
10425
|
|
|
10427
|
-
1. CONTINUE \u2014 If you are making
|
|
10426
|
+
1. CONTINUE \u2014 If you are making progress, briefly note what you've done and what remains. Keep working.
|
|
10428
10427
|
|
|
10429
|
-
2. PIVOT \u2014 If your current approach isn't working,
|
|
10428
|
+
2. PIVOT \u2014 If your current approach isn't working, describe a different strategy and immediately try it.
|
|
10430
10429
|
|
|
10431
|
-
3. CHECKPOINT \u2014 If you've made partial progress
|
|
10430
|
+
3. CHECKPOINT \u2014 If you've made partial progress and want to save it, call task_complete with a summary. The user can continue later.
|
|
10432
10431
|
|
|
10433
|
-
Respond with your assessment, then take action
|
|
10432
|
+
Respond with your assessment, then take action.`;
|
|
10434
10433
|
}
|
|
10435
10434
|
/** Run a task through the agentic loop */
|
|
10436
10435
|
async run(task, context) {
|
|
10437
10436
|
const start = Date.now();
|
|
10438
10437
|
const taskTimeoutMs = this.options.taskTimeoutMs;
|
|
10439
|
-
const
|
|
10440
|
-
|
|
10441
|
-
let
|
|
10438
|
+
const selfEvalInterval = taskTimeoutMs;
|
|
10439
|
+
let nextSelfEval = start + selfEvalInterval;
|
|
10440
|
+
let selfEvalCount = 0;
|
|
10442
10441
|
const toolCallLog = [];
|
|
10443
10442
|
this.aborted = false;
|
|
10444
10443
|
this.pendingUserMessages.length = 0;
|
|
@@ -10467,24 +10466,20 @@ TASK: ${task}` : task }
|
|
|
10467
10466
|
break;
|
|
10468
10467
|
}
|
|
10469
10468
|
const now = Date.now();
|
|
10470
|
-
if (now >
|
|
10471
|
-
|
|
10472
|
-
break;
|
|
10473
|
-
}
|
|
10474
|
-
if (!softTimeoutTriggered && now > softDeadline) {
|
|
10475
|
-
softTimeoutTriggered = true;
|
|
10469
|
+
if (now > nextSelfEval) {
|
|
10470
|
+
selfEvalCount++;
|
|
10476
10471
|
const elapsed = now - start;
|
|
10477
|
-
const remaining = hardDeadline - now;
|
|
10478
10472
|
const repetitionScore = this.detectRepetition(toolCallLog);
|
|
10479
10473
|
this.emit({
|
|
10480
10474
|
type: "compaction",
|
|
10481
|
-
content: `
|
|
10475
|
+
content: `Health check #${selfEvalCount} (${(elapsed / 6e4).toFixed(1)}m elapsed) \u2014 assessing progress`,
|
|
10482
10476
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10483
10477
|
});
|
|
10484
10478
|
messages.push({
|
|
10485
10479
|
role: "user",
|
|
10486
|
-
content: this.
|
|
10480
|
+
content: this.buildHealthCheckPrompt(elapsed, toolCallCount, repetitionScore, selfEvalCount)
|
|
10487
10481
|
});
|
|
10482
|
+
nextSelfEval = now + selfEvalInterval;
|
|
10488
10483
|
}
|
|
10489
10484
|
while (this.pendingUserMessages.length > 0) {
|
|
10490
10485
|
const userMsg = this.pendingUserMessages.shift();
|
|
@@ -10693,7 +10688,7 @@ ${result.output.length > maxLen ? this.foldOutput(result.output, maxLen) : resul
|
|
|
10693
10688
|
});
|
|
10694
10689
|
}
|
|
10695
10690
|
}
|
|
10696
|
-
while (!completed && !this.aborted && this.options.bruteForce && bruteForceCycle < this.options.bruteForceMaxCycles
|
|
10691
|
+
while (!completed && !this.aborted && this.options.bruteForce && bruteForceCycle < this.options.bruteForceMaxCycles) {
|
|
10697
10692
|
bruteForceCycle++;
|
|
10698
10693
|
const totalTurns = messages.filter((m) => m.role === "assistant").length;
|
|
10699
10694
|
this.emit({
|
|
@@ -10727,24 +10722,20 @@ You have ${this.options.maxTurns} more turns. Continue making progress. Call tas
|
|
|
10727
10722
|
break;
|
|
10728
10723
|
}
|
|
10729
10724
|
const bfNow = Date.now();
|
|
10730
|
-
if (bfNow >
|
|
10731
|
-
|
|
10732
|
-
break;
|
|
10733
|
-
}
|
|
10734
|
-
if (!softTimeoutTriggered && bfNow > softDeadline) {
|
|
10735
|
-
softTimeoutTriggered = true;
|
|
10725
|
+
if (bfNow > nextSelfEval) {
|
|
10726
|
+
selfEvalCount++;
|
|
10736
10727
|
const elapsed = bfNow - start;
|
|
10737
|
-
const remaining = hardDeadline - bfNow;
|
|
10738
10728
|
const repetitionScore = this.detectRepetition(toolCallLog);
|
|
10739
10729
|
this.emit({
|
|
10740
10730
|
type: "compaction",
|
|
10741
|
-
content: `
|
|
10731
|
+
content: `Health check #${selfEvalCount} (${(elapsed / 6e4).toFixed(1)}m elapsed) \u2014 assessing progress`,
|
|
10742
10732
|
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
10743
10733
|
});
|
|
10744
10734
|
messages.push({
|
|
10745
10735
|
role: "user",
|
|
10746
|
-
content: this.
|
|
10736
|
+
content: this.buildHealthCheckPrompt(elapsed, toolCallCount, repetitionScore, selfEvalCount)
|
|
10747
10737
|
});
|
|
10738
|
+
nextSelfEval = bfNow + selfEvalInterval;
|
|
10748
10739
|
}
|
|
10749
10740
|
while (this.pendingUserMessages.length > 0) {
|
|
10750
10741
|
const userMsg = this.pendingUserMessages.shift();
|
|
@@ -11416,8 +11407,7 @@ ${newerSummary}` : newerSummary;
|
|
|
11416
11407
|
const resp = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
11417
11408
|
method: "POST",
|
|
11418
11409
|
headers: this.authHeaders(),
|
|
11419
|
-
body: JSON.stringify(body)
|
|
11420
|
-
signal: AbortSignal.timeout(request.timeoutMs)
|
|
11410
|
+
body: JSON.stringify(body)
|
|
11421
11411
|
});
|
|
11422
11412
|
if (!resp.ok) {
|
|
11423
11413
|
const text = await resp.text().catch(() => "");
|
|
@@ -11477,8 +11467,7 @@ ${newerSummary}` : newerSummary;
|
|
|
11477
11467
|
const resp = await fetch(`${this.baseUrl}/v1/chat/completions`, {
|
|
11478
11468
|
method: "POST",
|
|
11479
11469
|
headers: this.authHeaders(),
|
|
11480
|
-
body: JSON.stringify(body)
|
|
11481
|
-
signal: AbortSignal.timeout(request.timeoutMs)
|
|
11470
|
+
body: JSON.stringify(body)
|
|
11482
11471
|
});
|
|
11483
11472
|
if (!resp.ok) {
|
|
11484
11473
|
const text = await resp.text().catch(() => "");
|
|
@@ -18739,7 +18728,7 @@ function themeForTool(toolName) {
|
|
|
18739
18728
|
return THEME_DEFAULT;
|
|
18740
18729
|
}
|
|
18741
18730
|
}
|
|
18742
|
-
var DENSITY, WAVE, THEME_DEFAULT, THEME_FILE, THEME_SHELL, THEME_WEB, THEME_SEARCH, THEME_MEMORY, THEME_SKILL, THEME_TOOL_CREATE, BrailleSpinner;
|
|
18731
|
+
var DENSITY, WAVE, THEME_DEFAULT, THEME_FILE, THEME_SHELL, THEME_WEB, THEME_SEARCH, THEME_MEMORY, THEME_SKILL, THEME_TOOL_CREATE, DEFAULT_METRICS, BrailleSpinner;
|
|
18743
18732
|
var init_braille_spinner = __esm({
|
|
18744
18733
|
"packages/cli/dist/tui/braille-spinner.js"() {
|
|
18745
18734
|
"use strict";
|
|
@@ -18796,11 +18785,17 @@ var init_braille_spinner = __esm({
|
|
|
18796
18785
|
ramp: [237, 173, 174, 179, 180, 215, 216, 222, 229],
|
|
18797
18786
|
speed: 2
|
|
18798
18787
|
};
|
|
18788
|
+
DEFAULT_METRICS = {
|
|
18789
|
+
contextPct: 0,
|
|
18790
|
+
tokenRate: 0,
|
|
18791
|
+
isStreaming: false
|
|
18792
|
+
};
|
|
18799
18793
|
BrailleSpinner = class {
|
|
18800
18794
|
frame = 0;
|
|
18801
18795
|
timer = null;
|
|
18802
18796
|
theme = THEME_DEFAULT;
|
|
18803
18797
|
colorRamp = buildColorRamp(THEME_DEFAULT.ramp);
|
|
18798
|
+
_metrics = { ...DEFAULT_METRICS };
|
|
18804
18799
|
/** Start the animation, calling `onFrame` every tick (~80 ms). */
|
|
18805
18800
|
start(onFrame) {
|
|
18806
18801
|
this.frame = 0;
|
|
@@ -18817,14 +18812,15 @@ var init_braille_spinner = __esm({
|
|
|
18817
18812
|
}
|
|
18818
18813
|
this.frame = 0;
|
|
18819
18814
|
this.setTool(null);
|
|
18815
|
+
this._metrics = { ...DEFAULT_METRICS };
|
|
18820
18816
|
}
|
|
18821
18817
|
/** Whether the animation timer is active. */
|
|
18822
18818
|
get isRunning() {
|
|
18823
18819
|
return this.timer !== null;
|
|
18824
18820
|
}
|
|
18825
18821
|
/**
|
|
18826
|
-
* Set the active tool, changing the animation color theme
|
|
18827
|
-
* Pass null to return to the default idle theme.
|
|
18822
|
+
* Set the active tool, changing the animation color theme.
|
|
18823
|
+
* Pass null to return to the default idle theme (e.g. on tool_result).
|
|
18828
18824
|
*/
|
|
18829
18825
|
setTool(toolName) {
|
|
18830
18826
|
const next = themeForTool(toolName);
|
|
@@ -18833,21 +18829,68 @@ var init_braille_spinner = __esm({
|
|
|
18833
18829
|
this.colorRamp = buildColorRamp(next.ramp);
|
|
18834
18830
|
}
|
|
18835
18831
|
}
|
|
18832
|
+
/**
|
|
18833
|
+
* Update real-time metrics that drive animation dynamics.
|
|
18834
|
+
* Call frequently (e.g. on each metrics update / stream tick).
|
|
18835
|
+
*/
|
|
18836
|
+
setMetrics(metrics) {
|
|
18837
|
+
if (metrics.contextPct !== void 0)
|
|
18838
|
+
this._metrics.contextPct = metrics.contextPct;
|
|
18839
|
+
if (metrics.tokenRate !== void 0)
|
|
18840
|
+
this._metrics.tokenRate = metrics.tokenRate;
|
|
18841
|
+
if (metrics.isStreaming !== void 0)
|
|
18842
|
+
this._metrics.isStreaming = metrics.isStreaming;
|
|
18843
|
+
}
|
|
18836
18844
|
/**
|
|
18837
18845
|
* Render the current animation frame as an ANSI-colored string.
|
|
18838
|
-
*
|
|
18839
|
-
*
|
|
18840
|
-
*
|
|
18846
|
+
*
|
|
18847
|
+
* State-driven dynamics:
|
|
18848
|
+
* - Speed: base from tool theme, boosted by token rate when streaming,
|
|
18849
|
+
* modulated by a slow breathing oscillation when idle.
|
|
18850
|
+
* - Amplitude: context pressure scales how much of the density range is
|
|
18851
|
+
* used — gentle ripples at low usage, full waves at high usage.
|
|
18852
|
+
* - Slinky: a secondary slow sine wave creates organic compression and
|
|
18853
|
+
* expansion across columns, making the wave feel elastic and alive.
|
|
18841
18854
|
*/
|
|
18842
18855
|
render(width) {
|
|
18843
18856
|
const cycleLen = WAVE.length;
|
|
18844
|
-
const
|
|
18857
|
+
const baseSpeed = this.theme.speed;
|
|
18858
|
+
const m = this._metrics;
|
|
18859
|
+
const breathPhase = Math.sin(this.frame * 0.08);
|
|
18860
|
+
let speed;
|
|
18861
|
+
if (m.isStreaming && m.tokenRate > 0) {
|
|
18862
|
+
const rateBoost = Math.min(4, m.tokenRate / 8);
|
|
18863
|
+
speed = baseSpeed + rateBoost + breathPhase * 0.3;
|
|
18864
|
+
} else {
|
|
18865
|
+
speed = baseSpeed + breathPhase * 0.8;
|
|
18866
|
+
}
|
|
18867
|
+
const pressure = Math.max(0, Math.min(100, m.contextPct)) / 100;
|
|
18868
|
+
const densityScale = 0.3 + pressure * 0.7;
|
|
18869
|
+
const slinkyFreq = 0.02 + (m.isStreaming ? 0.01 : 0);
|
|
18870
|
+
const slinkyAmp = 1.5 + pressure * 2;
|
|
18845
18871
|
let buf = "";
|
|
18846
18872
|
let lastColor = -1;
|
|
18847
18873
|
for (let col = 0; col < width; col++) {
|
|
18848
|
-
const
|
|
18849
|
-
const
|
|
18850
|
-
const
|
|
18874
|
+
const slinkyOffset = Math.sin(col * 0.1 + this.frame * slinkyFreq) * slinkyAmp;
|
|
18875
|
+
const rawPhase = col * speed + this.frame + slinkyOffset;
|
|
18876
|
+
const normalizedPhase = (rawPhase % cycleLen + cycleLen) % cycleLen;
|
|
18877
|
+
const waveIdx = Math.round(normalizedPhase) % cycleLen;
|
|
18878
|
+
let amplitude;
|
|
18879
|
+
if (waveIdx <= 8) {
|
|
18880
|
+
amplitude = waveIdx;
|
|
18881
|
+
} else {
|
|
18882
|
+
amplitude = 16 - waveIdx;
|
|
18883
|
+
}
|
|
18884
|
+
const scaledAmplitude = Math.round(amplitude * densityScale);
|
|
18885
|
+
let scaledIdx;
|
|
18886
|
+
if (waveIdx <= 8) {
|
|
18887
|
+
scaledIdx = scaledAmplitude;
|
|
18888
|
+
} else {
|
|
18889
|
+
scaledIdx = 16 - scaledAmplitude;
|
|
18890
|
+
}
|
|
18891
|
+
scaledIdx = Math.max(0, Math.min(cycleLen - 1, scaledIdx));
|
|
18892
|
+
const ch = WAVE[scaledIdx];
|
|
18893
|
+
const color = this.colorRamp[scaledIdx];
|
|
18851
18894
|
if (color !== lastColor) {
|
|
18852
18895
|
buf += `\x1B[38;5;${color}m`;
|
|
18853
18896
|
lastColor = color;
|
|
@@ -18862,13 +18905,12 @@ var init_braille_spinner = __esm({
|
|
|
18862
18905
|
});
|
|
18863
18906
|
|
|
18864
18907
|
// packages/cli/dist/tui/status-bar.js
|
|
18865
|
-
var
|
|
18908
|
+
var StatusBar;
|
|
18866
18909
|
var init_status_bar = __esm({
|
|
18867
18910
|
"packages/cli/dist/tui/status-bar.js"() {
|
|
18868
18911
|
"use strict";
|
|
18869
18912
|
init_render();
|
|
18870
18913
|
init_braille_spinner();
|
|
18871
|
-
FOOTER_ROWS = 5;
|
|
18872
18914
|
StatusBar = class {
|
|
18873
18915
|
metrics = {
|
|
18874
18916
|
promptTokens: 0,
|
|
@@ -18909,6 +18951,10 @@ var init_status_bar = __esm({
|
|
|
18909
18951
|
/** Whether agent is actively processing (braille animation) */
|
|
18910
18952
|
_processing = false;
|
|
18911
18953
|
_brailleSpinner = new BrailleSpinner();
|
|
18954
|
+
/** Current dynamic footer height (min 5: buffer + topSep + 1 input line + bottomSep + metrics) */
|
|
18955
|
+
_currentFooterHeight = 5;
|
|
18956
|
+
/** Timestamp when streaming started (for token rate calculation) */
|
|
18957
|
+
_streamStartTime = 0;
|
|
18912
18958
|
/**
|
|
18913
18959
|
* Provide a callback that returns readline's current input state.
|
|
18914
18960
|
* StatusBar uses this to render typed text and position the cursor
|
|
@@ -18950,11 +18996,13 @@ var init_status_bar = __esm({
|
|
|
18950
18996
|
return;
|
|
18951
18997
|
this._processing = active;
|
|
18952
18998
|
if (active) {
|
|
18999
|
+
this._brailleSpinner.setMetrics({ isStreaming: true });
|
|
18953
19000
|
this._brailleSpinner.start(() => {
|
|
18954
19001
|
if (this.active)
|
|
18955
19002
|
this.renderBufferLine();
|
|
18956
19003
|
});
|
|
18957
19004
|
} else {
|
|
19005
|
+
this._brailleSpinner.setMetrics({ isStreaming: false, tokenRate: 0 });
|
|
18958
19006
|
this._brailleSpinner.stop();
|
|
18959
19007
|
if (this.active)
|
|
18960
19008
|
this.renderBufferLine();
|
|
@@ -18994,15 +19042,43 @@ var init_status_bar = __esm({
|
|
|
18994
19042
|
this.metrics.totalTokens = update.totalTokens;
|
|
18995
19043
|
if (update.estimatedContextTokens !== void 0)
|
|
18996
19044
|
this.metrics.estimatedContextTokens = update.estimatedContextTokens;
|
|
19045
|
+
this._streamingTokens = 0;
|
|
19046
|
+
this._streamStartTime = 0;
|
|
19047
|
+
this.pushSpinnerContextMetrics();
|
|
19048
|
+
this._brailleSpinner.setMetrics({ tokenRate: 0, isStreaming: false });
|
|
18997
19049
|
if (this.active)
|
|
18998
19050
|
this.renderFooterPreserveCursor();
|
|
18999
19051
|
}
|
|
19052
|
+
/** Running count of tokens estimated during streaming (reset on authoritative updateMetrics) */
|
|
19053
|
+
_streamingTokens = 0;
|
|
19054
|
+
_streamThrottleTimer = null;
|
|
19055
|
+
/** Increment the live streaming token counter (throttled re-render at 100ms) */
|
|
19056
|
+
incrementStreamingTokens(count) {
|
|
19057
|
+
this._streamingTokens += count;
|
|
19058
|
+
if (this._streamStartTime === 0)
|
|
19059
|
+
this._streamStartTime = Date.now();
|
|
19060
|
+
const elapsedSec = (Date.now() - this._streamStartTime) / 1e3;
|
|
19061
|
+
const tokenRate = elapsedSec > 0.1 ? this._streamingTokens / elapsedSec : 0;
|
|
19062
|
+
this._brailleSpinner.setMetrics({ tokenRate, isStreaming: true });
|
|
19063
|
+
if (!this._streamThrottleTimer && this.active) {
|
|
19064
|
+
this._streamThrottleTimer = setTimeout(() => {
|
|
19065
|
+
this._streamThrottleTimer = null;
|
|
19066
|
+
if (this.active)
|
|
19067
|
+
this.renderFooterPreserveCursor();
|
|
19068
|
+
}, 100);
|
|
19069
|
+
}
|
|
19070
|
+
}
|
|
19071
|
+
/** Get the effective completion tokens (authoritative + live streaming estimate) */
|
|
19072
|
+
get effectiveCompletionTokens() {
|
|
19073
|
+
return this.metrics.completionTokens + this._streamingTokens;
|
|
19074
|
+
}
|
|
19000
19075
|
/** Reset metrics (e.g. on session start) */
|
|
19001
19076
|
resetMetrics() {
|
|
19002
19077
|
this.metrics.promptTokens = 0;
|
|
19003
19078
|
this.metrics.completionTokens = 0;
|
|
19004
19079
|
this.metrics.totalTokens = 0;
|
|
19005
19080
|
this.metrics.estimatedContextTokens = 0;
|
|
19081
|
+
this.pushSpinnerContextMetrics();
|
|
19006
19082
|
if (this.active)
|
|
19007
19083
|
this.renderFooterPreserveCursor();
|
|
19008
19084
|
}
|
|
@@ -19030,7 +19106,7 @@ var init_status_bar = __esm({
|
|
|
19030
19106
|
}
|
|
19031
19107
|
/**
|
|
19032
19108
|
* Set the prompt text that StatusBar draws on the input row.
|
|
19033
|
-
* Called by the REPL whenever the prompt changes (idle
|
|
19109
|
+
* Called by the REPL whenever the prompt changes (idle <-> active).
|
|
19034
19110
|
* The prompt is rendered as part of the atomic footer write so cursor
|
|
19035
19111
|
* positioning never depends on readline's internal tracking.
|
|
19036
19112
|
* @param text The ANSI-colored prompt string (e.g. "> " or "+ ")
|
|
@@ -19043,21 +19119,29 @@ var init_status_bar = __esm({
|
|
|
19043
19119
|
this.renderFooterAndPositionInput();
|
|
19044
19120
|
}
|
|
19045
19121
|
}
|
|
19046
|
-
/** Number of rows reserved at the bottom */
|
|
19122
|
+
/** Number of rows reserved at the bottom (dynamic based on input wrapping) */
|
|
19047
19123
|
get reservedRows() {
|
|
19048
|
-
return
|
|
19124
|
+
return this._currentFooterHeight;
|
|
19049
19125
|
}
|
|
19050
19126
|
/** Handle terminal resize — reapply scroll region and redraw footer */
|
|
19051
19127
|
handleResize() {
|
|
19052
19128
|
if (!this.active)
|
|
19053
19129
|
return;
|
|
19130
|
+
this.updateFooterHeight();
|
|
19131
|
+
const rows = process.stdout.rows ?? 24;
|
|
19132
|
+
const pos = this.rowPositions(rows);
|
|
19133
|
+
const w = getTermWidth();
|
|
19134
|
+
const sep = c2.dim("\u2500".repeat(w));
|
|
19054
19135
|
if (this.writeDepth > 0) {
|
|
19055
|
-
const
|
|
19056
|
-
|
|
19057
|
-
|
|
19058
|
-
|
|
19059
|
-
|
|
19060
|
-
|
|
19136
|
+
const inputWrap = this.wrapInput(w);
|
|
19137
|
+
let buf = `\x1B[${this.scrollRegionTop};${pos.scrollEnd}r\x1B[?25l\x1B[?7l\x1B[${pos.bufferRow};1H\x1B[2K${this.buildBufferContent(w)}\x1B[${pos.topSepRow};1H\x1B[2K${sep}`;
|
|
19138
|
+
for (let i = 0; i < inputWrap.lines.length; i++) {
|
|
19139
|
+
const row = pos.inputStartRow + i;
|
|
19140
|
+
const prefix = i === 0 ? this.promptText : " ".repeat(this.promptWidth);
|
|
19141
|
+
buf += `\x1B[${row};1H\x1B[2K${prefix}${inputWrap.lines[i]}`;
|
|
19142
|
+
}
|
|
19143
|
+
buf += `\x1B[${pos.bottomSepRow};1H\x1B[2K${sep}\x1B[${pos.metricsRow};1H\x1B[2K${this.buildMetricsLine()}\x1B[?7h\x1B[${pos.scrollEnd};1H`;
|
|
19144
|
+
process.stdout.write(buf);
|
|
19061
19145
|
} else {
|
|
19062
19146
|
this.applyScrollRegion();
|
|
19063
19147
|
this.renderFooterAndPositionInput();
|
|
@@ -19085,8 +19169,9 @@ var init_status_bar = __esm({
|
|
|
19085
19169
|
if (!this.active)
|
|
19086
19170
|
return;
|
|
19087
19171
|
this.writeDepth++;
|
|
19172
|
+
this._brailleSpinner.setMetrics({ isStreaming: true });
|
|
19088
19173
|
const rows = process.stdout.rows ?? 24;
|
|
19089
|
-
const scrollEnd = Math.max(rows -
|
|
19174
|
+
const scrollEnd = Math.max(rows - this._currentFooterHeight, this.scrollRegionTop + 1);
|
|
19090
19175
|
process.stdout.write(`\x1B[?25l\x1B[${this.scrollRegionTop};${scrollEnd}r\x1B[${scrollEnd};1H`);
|
|
19091
19176
|
}
|
|
19092
19177
|
/**
|
|
@@ -19101,6 +19186,7 @@ var init_status_bar = __esm({
|
|
|
19101
19186
|
return;
|
|
19102
19187
|
this.writeDepth = Math.max(0, this.writeDepth - 1);
|
|
19103
19188
|
if (this.writeDepth === 0) {
|
|
19189
|
+
this._brailleSpinner.setMetrics({ isStreaming: false });
|
|
19104
19190
|
this.renderFooterAndPositionInput();
|
|
19105
19191
|
}
|
|
19106
19192
|
}
|
|
@@ -19112,8 +19198,8 @@ var init_status_bar = __esm({
|
|
|
19112
19198
|
if (!this.active)
|
|
19113
19199
|
return;
|
|
19114
19200
|
const rows = process.stdout.rows ?? 24;
|
|
19115
|
-
const
|
|
19116
|
-
process.stdout.write(`\x1B[${
|
|
19201
|
+
const pos = this.rowPositions(rows);
|
|
19202
|
+
process.stdout.write(`\x1B[${pos.inputStartRow};1H\x1B[2K`);
|
|
19117
19203
|
}
|
|
19118
19204
|
/** Build the metrics line string */
|
|
19119
19205
|
buildMetricsLine() {
|
|
@@ -19122,7 +19208,8 @@ var init_status_bar = __esm({
|
|
|
19122
19208
|
const pipe = pastel2(60, " \u2502 ");
|
|
19123
19209
|
const tokIn = m.promptTokens > 0 ? m.promptTokens.toLocaleString() : `~${Math.max(m.estimatedContextTokens, 0).toLocaleString()}`;
|
|
19124
19210
|
const tokInLabel = pastel2(117, "In: ") + c2.bold(tokIn);
|
|
19125
|
-
const
|
|
19211
|
+
const effectiveOut = this.effectiveCompletionTokens;
|
|
19212
|
+
const tokOut = effectiveOut > 0 ? effectiveOut.toLocaleString() : `~${Math.ceil(m.totalTokens > 0 ? m.totalTokens - m.promptTokens : m.estimatedContextTokens * 0.3).toLocaleString()}`;
|
|
19126
19213
|
const tokOutLabel = pastel2(151, "Out: ") + c2.bold(tokOut);
|
|
19127
19214
|
const ctxUsed = m.estimatedContextTokens;
|
|
19128
19215
|
const ctxTotal = m.contextWindowSize;
|
|
@@ -19153,18 +19240,88 @@ var init_status_bar = __esm({
|
|
|
19153
19240
|
// -------------------------------------------------------------------------
|
|
19154
19241
|
// Private
|
|
19155
19242
|
// -------------------------------------------------------------------------
|
|
19156
|
-
/**
|
|
19243
|
+
/** Push current context window usage to the braille spinner */
|
|
19244
|
+
pushSpinnerContextMetrics() {
|
|
19245
|
+
const ctxUsed = this.metrics.estimatedContextTokens;
|
|
19246
|
+
const ctxTotal = this.metrics.contextWindowSize;
|
|
19247
|
+
const contextPct = ctxTotal > 0 ? Math.round(ctxUsed / ctxTotal * 100) : 0;
|
|
19248
|
+
this._brailleSpinner.setMetrics({ contextPct });
|
|
19249
|
+
}
|
|
19250
|
+
/** Compute how many visual lines the current input text occupies */
|
|
19251
|
+
computeInputLineCount(termWidth) {
|
|
19252
|
+
if (!this.inputStateProvider)
|
|
19253
|
+
return 1;
|
|
19254
|
+
const w = termWidth ?? getTermWidth();
|
|
19255
|
+
const availWidth = Math.max(1, w - this.promptWidth);
|
|
19256
|
+
const { line } = this.inputStateProvider();
|
|
19257
|
+
if (line.length <= availWidth)
|
|
19258
|
+
return 1;
|
|
19259
|
+
return Math.ceil(line.length / availWidth);
|
|
19260
|
+
}
|
|
19261
|
+
/** Update _currentFooterHeight based on current input. Returns true if height changed. */
|
|
19262
|
+
updateFooterHeight(termWidth) {
|
|
19263
|
+
const inputLines = this.computeInputLineCount(termWidth);
|
|
19264
|
+
const newHeight = 4 + inputLines;
|
|
19265
|
+
if (newHeight !== this._currentFooterHeight) {
|
|
19266
|
+
this._currentFooterHeight = newHeight;
|
|
19267
|
+
return true;
|
|
19268
|
+
}
|
|
19269
|
+
return false;
|
|
19270
|
+
}
|
|
19271
|
+
/** Compute absolute row positions for all footer elements */
|
|
19272
|
+
rowPositions(rows) {
|
|
19273
|
+
const fh = this._currentFooterHeight;
|
|
19274
|
+
return {
|
|
19275
|
+
scrollEnd: Math.max(rows - fh, this.scrollRegionTop + 1),
|
|
19276
|
+
bufferRow: rows - fh + 1,
|
|
19277
|
+
topSepRow: rows - fh + 2,
|
|
19278
|
+
inputStartRow: rows - fh + 3,
|
|
19279
|
+
bottomSepRow: rows - 1,
|
|
19280
|
+
metricsRow: rows
|
|
19281
|
+
};
|
|
19282
|
+
}
|
|
19283
|
+
/**
|
|
19284
|
+
* Wrap input text into lines of availWidth characters.
|
|
19285
|
+
* Returns the lines, plus cursor position within the wrapped layout.
|
|
19286
|
+
*/
|
|
19287
|
+
wrapInput(termWidth) {
|
|
19288
|
+
const availWidth = Math.max(1, termWidth - this.promptWidth);
|
|
19289
|
+
const inputState = this.inputStateProvider?.();
|
|
19290
|
+
const fullLine = inputState?.line ?? "";
|
|
19291
|
+
const cursorPos = inputState?.cursor ?? 0;
|
|
19292
|
+
if (fullLine.length <= availWidth) {
|
|
19293
|
+
return {
|
|
19294
|
+
lines: [fullLine],
|
|
19295
|
+
cursorRow: 0,
|
|
19296
|
+
cursorCol: this.promptWidth + cursorPos + 1
|
|
19297
|
+
};
|
|
19298
|
+
}
|
|
19299
|
+
const lines = [];
|
|
19300
|
+
for (let i = 0; i < fullLine.length; i += availWidth) {
|
|
19301
|
+
lines.push(fullLine.slice(i, i + availWidth));
|
|
19302
|
+
}
|
|
19303
|
+
if (lines.length === 0)
|
|
19304
|
+
lines.push("");
|
|
19305
|
+
const cursorLineIdx = Math.min(Math.floor(cursorPos / availWidth), lines.length - 1);
|
|
19306
|
+
const cursorColInLine = cursorPos - cursorLineIdx * availWidth;
|
|
19307
|
+
return {
|
|
19308
|
+
lines,
|
|
19309
|
+
cursorRow: cursorLineIdx,
|
|
19310
|
+
cursorCol: this.promptWidth + cursorColInLine + 1
|
|
19311
|
+
};
|
|
19312
|
+
}
|
|
19313
|
+
/** Set the DECSTBM scroll region to exclude the dynamic footer rows */
|
|
19157
19314
|
applyScrollRegion() {
|
|
19315
|
+
this.updateFooterHeight();
|
|
19158
19316
|
const rows = process.stdout.rows ?? 24;
|
|
19159
|
-
const
|
|
19160
|
-
process.stdout.write(`\x1B[${this.scrollRegionTop};${scrollEnd}r\x1B[${scrollEnd};1H`);
|
|
19317
|
+
const pos = this.rowPositions(rows);
|
|
19318
|
+
process.stdout.write(`\x1B[${this.scrollRegionTop};${pos.scrollEnd}r\x1B[${pos.scrollEnd};1H`);
|
|
19161
19319
|
}
|
|
19162
19320
|
/**
|
|
19163
19321
|
* Draw the COMPLETE footer — separators, prompt, metrics — in a single
|
|
19164
|
-
* atomic process.stdout.write() call.
|
|
19165
|
-
*
|
|
19166
|
-
*
|
|
19167
|
-
* movements that conflict with our absolute ANSI positioning).
|
|
19322
|
+
* atomic process.stdout.write() call. Input text wraps across multiple
|
|
19323
|
+
* rows when it exceeds the available width, and the footer dynamically
|
|
19324
|
+
* grows/shrinks to accommodate.
|
|
19168
19325
|
*
|
|
19169
19326
|
* Does NOT set DECSTBM — the scroll region is maintained by
|
|
19170
19327
|
* applyScrollRegion() and beginContentWrite().
|
|
@@ -19174,25 +19331,21 @@ var init_status_bar = __esm({
|
|
|
19174
19331
|
return;
|
|
19175
19332
|
const rows = process.stdout.rows ?? 24;
|
|
19176
19333
|
const w = getTermWidth();
|
|
19177
|
-
const
|
|
19178
|
-
const
|
|
19179
|
-
|
|
19180
|
-
|
|
19181
|
-
const cursorPos = inputState?.cursor ?? 0;
|
|
19182
|
-
const availWidth = Math.max(1, w - this.promptWidth);
|
|
19183
|
-
let visibleText;
|
|
19184
|
-
let cursorCol;
|
|
19185
|
-
if (fullLine.length <= availWidth) {
|
|
19186
|
-
visibleText = fullLine;
|
|
19187
|
-
cursorCol = this.promptWidth + cursorPos + 1;
|
|
19188
|
-
} else {
|
|
19189
|
-
const lookAhead = Math.min(8, Math.floor(availWidth / 4));
|
|
19190
|
-
let scrollOffset = cursorPos - (availWidth - lookAhead);
|
|
19191
|
-
scrollOffset = Math.max(0, Math.min(scrollOffset, fullLine.length - availWidth));
|
|
19192
|
-
visibleText = fullLine.slice(scrollOffset, scrollOffset + availWidth);
|
|
19193
|
-
cursorCol = this.promptWidth + (cursorPos - scrollOffset) + 1;
|
|
19334
|
+
const heightChanged = this.updateFooterHeight(w);
|
|
19335
|
+
const pos = this.rowPositions(rows);
|
|
19336
|
+
if (heightChanged) {
|
|
19337
|
+
process.stdout.write(`\x1B[${this.scrollRegionTop};${pos.scrollEnd}r`);
|
|
19194
19338
|
}
|
|
19195
|
-
const
|
|
19339
|
+
const sep = c2.dim("\u2500".repeat(w));
|
|
19340
|
+
const inputWrap = this.wrapInput(w);
|
|
19341
|
+
let buf = `\x1B[?7l\x1B[${pos.bufferRow};1H\x1B[2K${this.buildBufferContent(w)}\x1B[${pos.topSepRow};1H\x1B[2K${sep}`;
|
|
19342
|
+
for (let i = 0; i < inputWrap.lines.length; i++) {
|
|
19343
|
+
const row = pos.inputStartRow + i;
|
|
19344
|
+
const prefix = i === 0 ? this.promptText : " ".repeat(this.promptWidth);
|
|
19345
|
+
buf += `\x1B[${row};1H\x1B[2K${prefix}${inputWrap.lines[i]}`;
|
|
19346
|
+
}
|
|
19347
|
+
const cursorTermRow = pos.inputStartRow + inputWrap.cursorRow;
|
|
19348
|
+
buf += `\x1B[${pos.bottomSepRow};1H\x1B[2K${sep}\x1B[${pos.metricsRow};1H\x1B[2K${this.buildMetricsLine()}\x1B[?7h\x1B[?25h\x1B[${cursorTermRow};${inputWrap.cursorCol}H`;
|
|
19196
19349
|
process.stdout.write(buf);
|
|
19197
19350
|
}
|
|
19198
19351
|
/**
|
|
@@ -19201,50 +19354,66 @@ var init_status_bar = __esm({
|
|
|
19201
19354
|
* move the cursor away from where readline left it.
|
|
19202
19355
|
* Uses DEC DECSC/DECRC (\x1B7/\x1B8) for save/restore in a single write.
|
|
19203
19356
|
*
|
|
19357
|
+
* If the footer height has changed (due to input wrapping), falls back
|
|
19358
|
+
* to a full renderFooterAndPositionInput() instead.
|
|
19359
|
+
*
|
|
19204
19360
|
* IMPORTANT: Does NOT set DECSTBM here — setting the scroll region between
|
|
19205
19361
|
* cursor save/restore corrupts the restore position on many terminals.
|
|
19206
|
-
* The scroll region is enforced by beginContentWrite() and renderFooterAndPositionInput().
|
|
19207
19362
|
*/
|
|
19208
19363
|
renderFooterPreserveCursor() {
|
|
19209
19364
|
if (!this.active)
|
|
19210
19365
|
return;
|
|
19366
|
+
if (this.updateFooterHeight()) {
|
|
19367
|
+
if (this.writeDepth === 0) {
|
|
19368
|
+
this.renderFooterAndPositionInput();
|
|
19369
|
+
}
|
|
19370
|
+
return;
|
|
19371
|
+
}
|
|
19211
19372
|
const rows = process.stdout.rows ?? 24;
|
|
19212
19373
|
const w = getTermWidth();
|
|
19374
|
+
const pos = this.rowPositions(rows);
|
|
19213
19375
|
const sep = c2.dim("\u2500".repeat(w));
|
|
19214
|
-
const buf = `\x1B7\x1B[?7l\x1B[${
|
|
19376
|
+
const buf = `\x1B7\x1B[?7l\x1B[${pos.bufferRow};1H\x1B[2K${this.buildBufferContent(w)}\x1B[${pos.topSepRow};1H\x1B[2K${sep}\x1B[${pos.bottomSepRow};1H\x1B[2K${sep}\x1B[${pos.metricsRow};1H\x1B[2K${this.buildMetricsLine()}\x1B[?7h\x1B8`;
|
|
19215
19377
|
process.stdout.write(buf);
|
|
19216
19378
|
}
|
|
19217
19379
|
/**
|
|
19218
|
-
* Render
|
|
19380
|
+
* Render the input rows during an active content write (streaming).
|
|
19219
19381
|
* Uses DEC save/restore cursor so the streaming cursor position is preserved.
|
|
19220
|
-
*
|
|
19382
|
+
* If footer height changed, also updates DECSTBM and redraws full footer.
|
|
19221
19383
|
*/
|
|
19222
19384
|
renderInputRowDuringStream() {
|
|
19223
19385
|
if (!this.active || !this.inputStateProvider)
|
|
19224
19386
|
return;
|
|
19225
19387
|
const rows = process.stdout.rows ?? 24;
|
|
19226
19388
|
const w = getTermWidth();
|
|
19227
|
-
const
|
|
19228
|
-
const
|
|
19229
|
-
const
|
|
19230
|
-
|
|
19231
|
-
|
|
19232
|
-
|
|
19233
|
-
|
|
19234
|
-
|
|
19235
|
-
|
|
19236
|
-
|
|
19389
|
+
const heightChanged = this.updateFooterHeight(w);
|
|
19390
|
+
const pos = this.rowPositions(rows);
|
|
19391
|
+
const inputWrap = this.wrapInput(w);
|
|
19392
|
+
let buf = "\x1B7\x1B[?7l";
|
|
19393
|
+
if (heightChanged) {
|
|
19394
|
+
buf += `\x1B[${this.scrollRegionTop};${pos.scrollEnd}r`;
|
|
19395
|
+
const sep = c2.dim("\u2500".repeat(w));
|
|
19396
|
+
buf += `\x1B[${pos.bufferRow};1H\x1B[2K${this.buildBufferContent(w)}`;
|
|
19397
|
+
buf += `\x1B[${pos.topSepRow};1H\x1B[2K${sep}`;
|
|
19398
|
+
for (let i = 0; i < inputWrap.lines.length; i++) {
|
|
19399
|
+
const row = pos.inputStartRow + i;
|
|
19400
|
+
const prefix = i === 0 ? this.promptText : " ".repeat(this.promptWidth);
|
|
19401
|
+
buf += `\x1B[${row};1H\x1B[2K${prefix}${inputWrap.lines[i]}`;
|
|
19402
|
+
}
|
|
19403
|
+
buf += `\x1B[${pos.bottomSepRow};1H\x1B[2K${sep}`;
|
|
19404
|
+
buf += `\x1B[${pos.metricsRow};1H\x1B[2K${this.buildMetricsLine()}`;
|
|
19237
19405
|
} else {
|
|
19238
|
-
|
|
19239
|
-
|
|
19240
|
-
|
|
19241
|
-
|
|
19242
|
-
|
|
19406
|
+
for (let i = 0; i < inputWrap.lines.length; i++) {
|
|
19407
|
+
const row = pos.inputStartRow + i;
|
|
19408
|
+
const prefix = i === 0 ? this.promptText : " ".repeat(this.promptWidth);
|
|
19409
|
+
buf += `\x1B[${row};1H\x1B[2K${prefix}${inputWrap.lines[i]}`;
|
|
19410
|
+
}
|
|
19243
19411
|
}
|
|
19244
|
-
|
|
19412
|
+
buf += "\x1B[?7h\x1B8";
|
|
19413
|
+
process.stdout.write(buf);
|
|
19245
19414
|
}
|
|
19246
19415
|
/**
|
|
19247
|
-
* Build the content for the buffer line
|
|
19416
|
+
* Build the content for the buffer line.
|
|
19248
19417
|
* Returns braille animation when processing, empty string when idle.
|
|
19249
19418
|
*/
|
|
19250
19419
|
buildBufferContent(width) {
|
|
@@ -19254,7 +19423,7 @@ var init_status_bar = __esm({
|
|
|
19254
19423
|
return "";
|
|
19255
19424
|
}
|
|
19256
19425
|
/**
|
|
19257
|
-
* Render ONLY the buffer line
|
|
19426
|
+
* Render ONLY the buffer line using DEC save/restore cursor.
|
|
19258
19427
|
* Called by the braille spinner timer without disrupting scroll or input.
|
|
19259
19428
|
*/
|
|
19260
19429
|
renderBufferLine() {
|
|
@@ -19262,15 +19431,15 @@ var init_status_bar = __esm({
|
|
|
19262
19431
|
return;
|
|
19263
19432
|
const rows = process.stdout.rows ?? 24;
|
|
19264
19433
|
const w = getTermWidth();
|
|
19265
|
-
const
|
|
19434
|
+
const pos = this.rowPositions(rows);
|
|
19266
19435
|
const content = this.buildBufferContent(w);
|
|
19267
|
-
process.stdout.write(`\x1B7\x1B[?7l\x1B[${bufferRow};1H\x1B[2K${content}\x1B[?7h\x1B8`);
|
|
19436
|
+
process.stdout.write(`\x1B7\x1B[?7l\x1B[${pos.bufferRow};1H\x1B[2K${content}\x1B[?7h\x1B8`);
|
|
19268
19437
|
}
|
|
19269
19438
|
/**
|
|
19270
19439
|
* Hook into process.stdin to redraw footer after every keystroke.
|
|
19271
19440
|
* Since readline's output is suppressed (redirected to a no-op stream),
|
|
19272
19441
|
* this hook is responsible for rendering typed text on the input row.
|
|
19273
|
-
* During streaming (writeDepth > 0), only the input
|
|
19442
|
+
* During streaming (writeDepth > 0), only the input rows are updated using
|
|
19274
19443
|
* cursor save/restore so the streaming position isn't disrupted.
|
|
19275
19444
|
* When idle (writeDepth === 0), the full footer is redrawn.
|
|
19276
19445
|
*/
|
|
@@ -19505,7 +19674,7 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
|
|
|
19505
19674
|
streamEnabled: stream?.enabled ?? false,
|
|
19506
19675
|
bruteForce: bruteForce ?? true,
|
|
19507
19676
|
bruteForceMaxCycles: 100,
|
|
19508
|
-
// effectively unlimited — hard timeout
|
|
19677
|
+
// effectively unlimited — no hard timeout, agent runs until complete or aborted
|
|
19509
19678
|
contextWindowSize: contextWindowSize ?? 0
|
|
19510
19679
|
});
|
|
19511
19680
|
const tools = buildTools(repoRoot, config);
|
|
@@ -19594,6 +19763,10 @@ function startTask(task, config, repoRoot, voice, stream, taskStores, bruteForce
|
|
|
19594
19763
|
if (stream?.enabled) {
|
|
19595
19764
|
stream.renderer.write(event.content ?? "", event.streamKind ?? "content");
|
|
19596
19765
|
}
|
|
19766
|
+
if (statusBar && event.content) {
|
|
19767
|
+
const estimatedNewTokens = Math.max(1, Math.ceil(event.content.length / 4));
|
|
19768
|
+
statusBar.incrementStreamingTokens(estimatedNewTokens);
|
|
19769
|
+
}
|
|
19597
19770
|
break;
|
|
19598
19771
|
case "stream_end":
|
|
19599
19772
|
if (stream?.enabled) {
|
|
@@ -19886,7 +20059,7 @@ async function startInteractive(config, repoPath) {
|
|
|
19886
20059
|
let sessionToolCallCount = 0;
|
|
19887
20060
|
let sessionSudoPassword = null;
|
|
19888
20061
|
let sudoPromptPending = false;
|
|
19889
|
-
const idlePrompt = `${c2.bold(c2.white("\
|
|
20062
|
+
const idlePrompt = `${c2.bold(c2.white("\u276F "))}`;
|
|
19890
20063
|
const activePrompt = `${c2.bold(c2.white("+ "))}`;
|
|
19891
20064
|
const rl = readline2.createInterface({
|
|
19892
20065
|
input: process.stdin,
|
package/package.json
CHANGED