aboutsummaryrefslogtreecommitdiff
path: root/modules/core/core-bundle.js
diff options
context:
space:
mode:
Diffstat (limited to 'modules/core/core-bundle.js')
-rw-r--r--modules/core/core-bundle.js41
1 files changed, 31 insertions, 10 deletions
diff --git a/modules/core/core-bundle.js b/modules/core/core-bundle.js
index ef2d425..cf805a9 100644
--- a/modules/core/core-bundle.js
+++ b/modules/core/core-bundle.js
@@ -9699,18 +9699,24 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re
if (!userMessage) {
this.terminal.print('Usage: ai <your-question>', 'warning'); return;
}
- this._cancelled = false;
- this.terminal.setAiBusy(true);
+ // A delegated run owns its own session (see _isAgentRun below) and must not queue
+ // behind the main turn: orchestrate-parallel's fan-out used to run strictly one
+ // sub-task at a time, and a waiter that hit the timeout cleared a lock it did not
+ // own — which released the main turn's lock while it was still running.
+ const _agentTag = (opts && opts.agentTag) || null;
+ if (!_agentTag) this._cancelled = false;
const lockStart = Date.now();
- while (this._chatLock) {
+ while (!_agentTag && this._chatLock) {
if (Date.now() - lockStart > this._chatLockTimeoutMs) {
- this._chatLock = false;
- this.terminal.print('⚠️ Chat lock timed out — force released', 'warning');
- break;
+ throw new Error("chat lock busy after " + Math.round(this._chatLockTimeoutMs / 1000)
+ + "s — refusing to release another turn's lock");
}
await new Promise(r => setTimeout(r, 50));
}
- this._chatLock = true;
+ if (!_agentTag) this._chatLock = true;
+ const _prevTag = this._currentTag;
+ this._currentTag = _agentTag || 'main';
+ this.terminal.setAiBusy(true);
const taskStart = Date.now(); // predictor durations exclude chat-lock wait
this.terminal.markDirty();
// Auto-route context based on intent
@@ -9867,7 +9873,7 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re
try {
var om = this.terminal ? this.terminal.ollamaManager : null;
var usageData = om && om._usageLog && om._usageLog.length > 0 ? om._usageLog[om._usageLog.length - 1] : null;
- this._postRunReview(loopResult, this._toolCallLog.slice(-(loopResult.toolTurns || 0)), usageData);
+ this._postRunReview(loopResult, this._recentToolLog(_agentTag || 'main', loopResult.toolTurns || 0), usageData);
} catch (e) { /* self-improvement is best-effort */ }
}
// Auto-create skills from repeated errors
@@ -9884,7 +9890,8 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re
setTimeout(() => this.terminal.playNotificationSound(), 100);
}
} finally {
- this._chatLock = false;
+ this._currentTag = _prevTag;
+ if (!_agentTag) this._chatLock = false;
this.terminal.setAiBusy(false);
// Persist only the user's conversation — agent sessions are ephemeral.
if (!_isAgentRun) this._saveContext();
@@ -9894,7 +9901,7 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re
// `recent` was referenced but never declared: the bare identifier threw
// ReferenceError inside this finally block, rejecting chat() on every
// tool-using turn (shipped in 32608bc, caught by the caller sweep).
- const recent = this._toolCallLog.slice(-(loopResult.toolTurns || 0));
+ const recent = this._recentToolLog(_agentTag || 'main', loopResult.toolTurns || 0);
const toolNames = recent.map(function(e) { return e.name; }).join(', ');
this.terminal.log('INFO', 'AI', 'Used ' + loopResult.toolTurns + ' tool(s): ' + toolNames
+ ' · returned ' + Math.round(recent.reduce((a, e) => a + e.returnedBytes, 0) / 1024) + 'KB'
@@ -10231,6 +10238,19 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re
// went 6 → 112 705 tokens across 8 model calls (maxContextTokens is 12 000). Two
// limits keep a single turn inside the window: clip each observation as it arrives,
// and elide the oldest ones once the running total passes the budget.
+ // A run may only see the tool calls it made: with concurrent delegated runs the
+ // raw ring buffer interleaves, and slicing it by toolTurns handed one agent
+ // another agent's entries.
+ _recentToolLog(tag, n) {
+ const want = tag || 'main';
+ const out = [];
+ const cap = Math.max(0, Number(n) || 0);
+ for (let i = this._toolCallLog.length - 1; i >= 0 && out.length < cap; i--) {
+ const e = this._toolCallLog[i];
+ if ((e.tag || 'main') === want) out.unshift(e);
+ }
+ return out;
+ }
_recordToolCall(entry) {
const r = String(entry.result || '');
let truncated = false, contentChars = null;
@@ -10250,6 +10270,7 @@ max_chars: { type: 'integer', description: 'Max characters of page content to re
clippedBytes: Math.max(0, r.length - Number(entry.returnedBytes || r.length)),
truncated: truncated, durationMs: entry.durationMs || 0,
ok: !entry.error, errorKind: entry.errorKind || null,
+ tag: entry.tag || this._currentTag || 'main',
};
this._toolCallLog.push(rec);
if (this._toolCallLog.length > 50) this._toolCallLog.shift();