@xingjisoft/neutrino 0.5.2 → 0.6.2
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/README.md +1 -0
- package/assets/fast.txt +18 -0
- package/assets/pro/common.txt +4 -0
- package/assets/pro/main_model.txt +2 -0
- package/assets/pro/plan_model.txt +3 -1
- package/assets/prompt.txt +9 -0
- package/assets/ptc.txt +27 -0
- package/assets/subagent.txt +22 -1
- package/dist/app.js +2 -0
- package/dist/config.js +33 -1
- package/dist/llm/client.js +61 -8
- package/dist/llm/codeExecutor.js +239 -0
- package/dist/llm/codemode.js +164 -0
- package/dist/llm/context.js +110 -0
- package/dist/llm/plan.js +97 -9
- package/dist/llm/risk.js +10 -0
- package/dist/llm/runtime.js +382 -0
- package/dist/llm/search.js +426 -0
- package/dist/llm/searchNetwork.js +129 -0
- package/dist/llm/subagent.js +247 -112
- package/dist/llm/tools.js +101 -4
- package/dist/pro/agents.js +49 -19
- package/dist/pro/preflight.js +24 -2
- package/dist/screens/chat.js +337 -69
- package/dist/web/hub.js +9 -0
- package/dist/web/server.js +57 -8
- package/dist/web/session.js +342 -62
- package/dist/web/terminalBridge.js +10 -1
- package/package.json +10 -5
- package/web/README.md +3 -2
- package/web/app.js +276 -51
- package/web/i18n.js +20 -0
- package/web/index.html +10 -0
- package/web/style.css +57 -11
- package/web/turns.js +7 -8
package/dist/web/session.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import path from "node:path";
|
|
1
2
|
/**
|
|
2
3
|
* Headless conversation runner for the web UI.
|
|
3
4
|
*
|
|
@@ -11,15 +12,18 @@
|
|
|
11
12
|
* (snapshot, stream, pause, resume, message) work unchanged.
|
|
12
13
|
*/
|
|
13
14
|
import { randomUUID } from "node:crypto";
|
|
14
|
-
import { findModel, contextCompressionThreshold, intensityForModel, loadConfig, loadPlannerPrompt, loadStepPrompt, loadSubAgentPrompt, normalizeApprovalMode, planFirstEnabled, loadModels,
|
|
15
|
+
import { findModel, contextCompressionThreshold, modelContextWindow, sessionDir, intensityForModel, loadConfig, loadPlannerPrompt, loadStepPrompt, loadSubAgentPrompt, normalizeApprovalMode, planFirstEnabled, loadModels, loadAgentPrompt, normalizeAgentMode, saveSession, } from "../config.js";
|
|
15
16
|
import { COMPACT_KEEP, planCompaction, transcriptFor, modelHistory, normalizeContext, } from "../compact.js";
|
|
17
|
+
import { TaskRuntime, TaskControlError, currentTask, linkSignals, taskLimits, taskBudgetScope, resumeTaskSnapshot } from "../llm/runtime.js";
|
|
18
|
+
import { prepareContext, outputAllowance, finalTaskReport } from "../llm/context.js";
|
|
19
|
+
import { FINISH_STEP_TOOL, FINISH_STEP_DEFINITION, finishPlanStep, preparePlan, settlePlan } from "../llm/plan.js";
|
|
16
20
|
import { LLMClient, describeError } from "../llm/client.js";
|
|
17
21
|
import { defaultProPreset, loadProModels, loadProPrompt, proMaxRecursionDepth, requireProPreset } from "../pro/config.js";
|
|
18
22
|
import { resolveProTeam, ProPreflightError } from "../pro/preflight.js";
|
|
19
23
|
import { ProAgentTeam, getProToolDefinitions } from "../pro/agents.js";
|
|
20
24
|
import { advancePlan, buildPlannerInput, lastRequestText, needsPlan, parsePlan, planMessage, planTranscript, renderStepDirective, resumePlan, stopPlan, } from "../llm/plan.js";
|
|
21
25
|
import { DELEGATE_TOOL, executeTool, getSubAgentToolDefinitions, getToolDefinitions } from "../llm/tools.js";
|
|
22
|
-
import { buildSubAgentTask, formatSubAgentResult, runSubAgent as runSubAgentTask, } from "../llm/subagent.js";
|
|
26
|
+
import { buildSubAgentTask, formatSubAgentResult, runSubAgent as runSubAgentTask, subAgentRounds, } from "../llm/subagent.js";
|
|
23
27
|
import { assessToolCall } from "../llm/risk.js";
|
|
24
28
|
import { ApprovalGate, captureApprovalContext, describeApprovalIntent } from "../llm/approval.js";
|
|
25
29
|
import { SessionCheckpoint } from "../persistence.js";
|
|
@@ -45,7 +49,7 @@ export function newSessionId(now = new Date()) {
|
|
|
45
49
|
* written to disk until the first message, so an abandoned new-chat dialog
|
|
46
50
|
* cannot litter the history with empty transcripts.
|
|
47
51
|
*/
|
|
48
|
-
export function createWebSession(cwd, selection, approvalMode, mode = "standard", proPreset) {
|
|
52
|
+
export function createWebSession(cwd, selection, approvalMode, mode = "standard", proPreset, agentMode = "standard") {
|
|
49
53
|
const now = new Date();
|
|
50
54
|
const source = {
|
|
51
55
|
id: newSessionId(now),
|
|
@@ -59,6 +63,7 @@ export function createWebSession(cwd, selection, approvalMode, mode = "standard"
|
|
|
59
63
|
messages: [],
|
|
60
64
|
tokens: { prompt: 0, completion: 0 },
|
|
61
65
|
approval_mode: normalizeApprovalMode(approvalMode ?? loadConfig(mode).approval_mode),
|
|
66
|
+
agent_mode: mode === "pro" ? "standard" : agentMode,
|
|
62
67
|
...(mode === "pro" ? { pro_preset: requireProPreset(proPreset ?? defaultProPreset()) } : {}),
|
|
63
68
|
};
|
|
64
69
|
return new WebSession(source, selection.model, mode);
|
|
@@ -161,7 +166,13 @@ export class WebSession {
|
|
|
161
166
|
this.publishLive();
|
|
162
167
|
return saved;
|
|
163
168
|
});
|
|
169
|
+
taskRuntime = null;
|
|
170
|
+
lastTask;
|
|
171
|
+
taskOutcome = "completed";
|
|
164
172
|
turnToken = 0;
|
|
173
|
+
agentMode = "standard";
|
|
174
|
+
activeAgentMode = "standard";
|
|
175
|
+
turnAbort = null;
|
|
165
176
|
lastPromptTokens = 0;
|
|
166
177
|
disposed = false;
|
|
167
178
|
idleTimer = null;
|
|
@@ -171,6 +182,7 @@ export class WebSession {
|
|
|
171
182
|
reviewing = null;
|
|
172
183
|
constructor(source, model, mode = "standard") {
|
|
173
184
|
this.mode = mode;
|
|
185
|
+
this.agentMode = mode === "pro" ? "standard" : normalizeAgentMode(source.agent_mode);
|
|
174
186
|
if (mode === "pro")
|
|
175
187
|
this.proPreset = requireProPreset(source.pro_preset ?? defaultProPreset());
|
|
176
188
|
this.model = model;
|
|
@@ -184,6 +196,7 @@ export class WebSession {
|
|
|
184
196
|
this.messages = source.messages.slice();
|
|
185
197
|
this.context = normalizeContext(source.context, this.messages.filter((m) => m.role !== "system").length);
|
|
186
198
|
this.plan = resumePlan(source.plan ?? null);
|
|
199
|
+
this.lastTask = resumeTaskSnapshot(source.task_run);
|
|
187
200
|
this.tokens = {
|
|
188
201
|
prompt: source.tokens?.prompt ?? 0,
|
|
189
202
|
completion: source.tokens?.completion ?? 0,
|
|
@@ -204,6 +217,7 @@ export class WebSession {
|
|
|
204
217
|
return;
|
|
205
218
|
this.disposed = true;
|
|
206
219
|
this.proAbort?.abort();
|
|
220
|
+
this.turnAbort?.abort();
|
|
207
221
|
this.turnToken++;
|
|
208
222
|
this.busy = false;
|
|
209
223
|
if (this.pendingApproval) {
|
|
@@ -267,6 +281,9 @@ export class WebSession {
|
|
|
267
281
|
model: this.model.name,
|
|
268
282
|
intensity: this.intensity,
|
|
269
283
|
approvalMode: this.approvalMode,
|
|
284
|
+
agentMode: this.agentMode,
|
|
285
|
+
activeAgentMode: this.busy ? this.activeAgentMode : this.agentMode,
|
|
286
|
+
task: this.taskRuntime?.snapshot() ?? this.lastTask,
|
|
270
287
|
busy: this.busy,
|
|
271
288
|
paused: this.paused,
|
|
272
289
|
queued: this.queued,
|
|
@@ -316,6 +333,7 @@ export class WebSession {
|
|
|
316
333
|
return;
|
|
317
334
|
this.paused = true;
|
|
318
335
|
this.proAbort?.abort();
|
|
336
|
+
this.turnAbort?.abort();
|
|
319
337
|
this.turnToken++;
|
|
320
338
|
// A queued decision would otherwise keep the promise (and the gate) alive.
|
|
321
339
|
if (this.pendingApproval) {
|
|
@@ -328,11 +346,9 @@ export class WebSession {
|
|
|
328
346
|
this.subagent = null;
|
|
329
347
|
if (this.proState)
|
|
330
348
|
this.proState.checking = false;
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
this.planning = false;
|
|
335
|
-
}
|
|
349
|
+
this.reviewing = null;
|
|
350
|
+
this.retrying = null;
|
|
351
|
+
this.planning = false;
|
|
336
352
|
stopPlan(this.plan);
|
|
337
353
|
this.save();
|
|
338
354
|
this.publishLive();
|
|
@@ -356,6 +372,14 @@ export class WebSession {
|
|
|
356
372
|
this.publishLive();
|
|
357
373
|
return true;
|
|
358
374
|
}
|
|
375
|
+
liveSetAgentMode(mode) {
|
|
376
|
+
if (this.mode === "pro" || this.disposed)
|
|
377
|
+
return { ok: false, error: "this session cannot change agent mode" };
|
|
378
|
+
this.agentMode = mode;
|
|
379
|
+
this.save();
|
|
380
|
+
this.publishLive();
|
|
381
|
+
return { ok: true };
|
|
382
|
+
}
|
|
359
383
|
liveSetApprovalMode(mode) {
|
|
360
384
|
if (this.approvalMode === mode)
|
|
361
385
|
return;
|
|
@@ -382,7 +406,10 @@ export class WebSession {
|
|
|
382
406
|
this.proAbort?.abort();
|
|
383
407
|
this.proTeam = null;
|
|
384
408
|
this.proState = undefined;
|
|
409
|
+
this.turnAbort?.abort();
|
|
385
410
|
this.turnToken++;
|
|
411
|
+
this.taskRuntime = null;
|
|
412
|
+
this.lastTask = undefined;
|
|
386
413
|
if (this.pendingApproval) {
|
|
387
414
|
const pending = this.pendingApproval;
|
|
388
415
|
this.pendingApproval = null;
|
|
@@ -430,9 +457,24 @@ export class WebSession {
|
|
|
430
457
|
this.startTurn();
|
|
431
458
|
}
|
|
432
459
|
startTurn() {
|
|
460
|
+
if (this.taskRuntime?.settling) {
|
|
461
|
+
const previous = this.taskRuntime;
|
|
462
|
+
this.queued = lastRequestText(this.messages);
|
|
463
|
+
void previous.drain().then(() => {
|
|
464
|
+
if (this.taskRuntime === previous && this.queued && !this.paused) {
|
|
465
|
+
this.queued = null;
|
|
466
|
+
this.startTurn();
|
|
467
|
+
}
|
|
468
|
+
});
|
|
469
|
+
this.publishLive();
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
433
472
|
const turnMessages = this.messages.slice();
|
|
434
473
|
const approvalContext = captureApprovalContext(turnMessages);
|
|
435
474
|
this.busy = true;
|
|
475
|
+
this.activeAgentMode = this.agentMode;
|
|
476
|
+
this.turnAbort?.abort();
|
|
477
|
+
this.turnAbort = new AbortController();
|
|
436
478
|
const token = ++this.turnToken;
|
|
437
479
|
this.streamingContent = "";
|
|
438
480
|
this.publishLive();
|
|
@@ -449,16 +491,59 @@ export class WebSession {
|
|
|
449
491
|
* pass, exactly as it did before planning existed.
|
|
450
492
|
*/
|
|
451
493
|
async runTurn(token, turnMessages, approvalContext) {
|
|
494
|
+
const runtime = new TaskRuntime({
|
|
495
|
+
request: approvalContext.prompt, priorUserRequests: approvalContext.priorUserRequests,
|
|
496
|
+
cwd: this.dir, signal: this.turnAbort?.signal, limits: taskLimits(loadConfig(this.mode)),
|
|
497
|
+
budgetScope: taskBudgetScope(loadConfig(this.mode)),
|
|
498
|
+
journalDir: path.join(sessionDir(this.mode), "tasks", path.basename(this.sessionId || "unsaved")),
|
|
499
|
+
});
|
|
500
|
+
this.taskRuntime = runtime;
|
|
501
|
+
this.taskOutcome = "completed";
|
|
502
|
+
let failed = false;
|
|
503
|
+
try {
|
|
504
|
+
await runtime.run(() => this.runTaskTurn(token, turnMessages, approvalContext));
|
|
505
|
+
}
|
|
506
|
+
catch (error) {
|
|
507
|
+
failed = true;
|
|
508
|
+
throw error;
|
|
509
|
+
}
|
|
510
|
+
finally {
|
|
511
|
+
await runtime.drain();
|
|
512
|
+
if (runtime.signal.aborted)
|
|
513
|
+
settlePlan(runtime.plan ?? null, true);
|
|
514
|
+
runtime.finish(runtime.signal.aborted ? "cancelled" : failed ? "failed" :
|
|
515
|
+
runtime.plan?.steps.some(step => step.status === "failed") ? "failed" :
|
|
516
|
+
runtime.plan?.steps.some(step => !["done", "skipped"].includes(step.status)) ? "partial" : this.taskOutcome);
|
|
517
|
+
if (this.taskRuntime === runtime && !this.disposed) {
|
|
518
|
+
if (runtime.signal.aborted && token === this.turnToken && this.busy && !this.paused) {
|
|
519
|
+
if (this.taskOutcome !== "cancelled")
|
|
520
|
+
this.messages.push({ role: "assistant", content: `[Task stopped: ${String(runtime.signal.reason)}]\n${runtime.partialReport()}` });
|
|
521
|
+
this.streamingContent = null;
|
|
522
|
+
this.onTurnFinished();
|
|
523
|
+
}
|
|
524
|
+
if (this.taskRuntime === runtime) {
|
|
525
|
+
this.lastTask = runtime.snapshot();
|
|
526
|
+
this.save();
|
|
527
|
+
this.publishLive();
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
async runTaskTurn(token, turnMessages, approvalContext) {
|
|
452
533
|
if (this.mode === "pro")
|
|
453
534
|
return this.runProTurn(token, turnMessages, approvalContext);
|
|
454
535
|
await this.autoCompress(token);
|
|
455
536
|
if (token !== this.turnToken || this.paused)
|
|
456
537
|
return;
|
|
457
|
-
|
|
538
|
+
if (this.activeAgentMode !== "standard")
|
|
539
|
+
this.plan = null;
|
|
540
|
+
const plan = this.activeAgentMode === "standard" ? await this.makePlan(token) : null;
|
|
458
541
|
if (token !== this.turnToken || this.paused)
|
|
459
542
|
return;
|
|
460
543
|
if (plan) {
|
|
461
544
|
this.plan = plan;
|
|
545
|
+
if (currentTask())
|
|
546
|
+
currentTask().runtime.plan = plan;
|
|
462
547
|
this.messages.push({ role: "assistant", content: planMessage(plan) });
|
|
463
548
|
this.save();
|
|
464
549
|
this.publishLive();
|
|
@@ -470,7 +555,7 @@ export class WebSession {
|
|
|
470
555
|
const finished = await this.agentLoop(this.buildApiMessages(this.turnView(turnMessages), renderStepDirective(template, plan, i)), token, approvalContext);
|
|
471
556
|
if (token !== this.turnToken || this.paused)
|
|
472
557
|
return;
|
|
473
|
-
this.setStep(i, finished ? "done" : "failed");
|
|
558
|
+
this.setStep(i, currentTask()?.signal.aborted ? "cancelled" : finished ? "done" : "failed");
|
|
474
559
|
if (!finished)
|
|
475
560
|
break;
|
|
476
561
|
}
|
|
@@ -484,7 +569,8 @@ export class WebSession {
|
|
|
484
569
|
}
|
|
485
570
|
/** Freeze one resolved team per task, plan first, then give the whole brief to main. */
|
|
486
571
|
async runProTurn(token, turnMessages, approvalContext) {
|
|
487
|
-
const
|
|
572
|
+
const taskSignal = currentTask()?.signal;
|
|
573
|
+
const cancelled = () => token !== this.turnToken || this.paused || this.disposed || taskSignal?.aborted === true;
|
|
488
574
|
const controller = new AbortController();
|
|
489
575
|
this.proAbort = controller;
|
|
490
576
|
this.proTeam = null;
|
|
@@ -509,6 +595,7 @@ export class WebSession {
|
|
|
509
595
|
this.proState.fallbacks = resolved.fallbacks;
|
|
510
596
|
this.proState.checking = false;
|
|
511
597
|
const team = new ProAgentTeam({
|
|
598
|
+
runtime: currentTask()?.runtime, request: approvalContext.prompt,
|
|
512
599
|
models: resolved.models, maxDepth: proMaxRecursionDepth(), cwd: this.dir, cancelled,
|
|
513
600
|
approve: (name, args) => this.requestApproval(name, args, {
|
|
514
601
|
...approvalContext, context: captureApprovalContext(this.turnView(turnMessages)).context,
|
|
@@ -537,48 +624,44 @@ export class WebSession {
|
|
|
537
624
|
this.publishLive();
|
|
538
625
|
this.planning = true;
|
|
539
626
|
this.publishLive();
|
|
540
|
-
const
|
|
627
|
+
const planningResult = await team.callResult("plan_model", buildPlannerInput({
|
|
541
628
|
request: approvalContext.prompt, cwd: this.dir, transcript: planTranscript(turnMessages),
|
|
542
629
|
}), "Plan the task before execution. Do not modify files or execute commands.", 0, true);
|
|
630
|
+
const planning = planningResult.report;
|
|
543
631
|
if (cancelled())
|
|
544
632
|
return;
|
|
545
633
|
this.planning = false;
|
|
546
634
|
this.subagent = null;
|
|
547
635
|
this.retrying = null;
|
|
548
|
-
if (!planning.trim()
|
|
549
|
-
throw new Error(`Pro planning failed: ${planning}`);
|
|
550
|
-
}
|
|
551
|
-
this.plan = parsePlan(planning);
|
|
552
|
-
if (!this.plan) {
|
|
553
|
-
// Standard planning skips single actions; Pro plans every task.
|
|
554
|
-
try {
|
|
555
|
-
const single = JSON.parse(planning.trim().replace(/^```(?:json)?\s*|\s*```$/g, ""));
|
|
556
|
-
if (single.steps?.length === 1 && typeof single.steps[0].title === "string" && single.steps[0].title.trim()) {
|
|
557
|
-
this.plan = { goal: String(single.goal ?? "").slice(0, 240), steps: [{
|
|
558
|
-
title: single.steps[0].title.slice(0, 160), detail: String(single.steps[0].detail ?? "").slice(0, 400), status: "pending",
|
|
559
|
-
}] };
|
|
560
|
-
}
|
|
561
|
-
}
|
|
562
|
-
catch { /* A useful prose plan can still guide the main agent. */ }
|
|
636
|
+
if (planningResult.status !== "completed" || !planning.trim()) {
|
|
637
|
+
throw new Error(`Pro planning failed: ${planningResult.error?.message ?? planning}`);
|
|
563
638
|
}
|
|
639
|
+
this.plan = parsePlan(planning, 1);
|
|
640
|
+
if (!this.plan)
|
|
641
|
+
throw new Error("Pro planning failed: no usable plan steps were returned");
|
|
564
642
|
await this.autoCompress(token);
|
|
565
643
|
if (cancelled())
|
|
566
644
|
return;
|
|
645
|
+
if (this.plan) {
|
|
646
|
+
preparePlan(this.plan);
|
|
647
|
+
if (currentTask())
|
|
648
|
+
currentTask().runtime.plan = this.plan;
|
|
649
|
+
}
|
|
567
650
|
this.messages.push({ role: "assistant", content: this.plan ? planMessage(this.plan) : planning });
|
|
568
651
|
this.save();
|
|
569
652
|
this.publishLive();
|
|
570
|
-
const directive = `The planning agent produced this plan for the current user request:\n${planning}\n\n` +
|
|
571
|
-
"Execute the complete task
|
|
653
|
+
const directive = `The planning agent produced this plan for the current user request:\n${JSON.stringify(this.plan ?? planning)}\n\n` +
|
|
654
|
+
"Execute the complete task using the appropriate professional agents. Own integration and verification. " +
|
|
655
|
+
"Call finish_step for each accepted step using its step_id and actual evidence IDs, existing artifact paths, or the final text deliverable. " +
|
|
656
|
+
"Record blocked or failed steps explicitly. Ending a reply never completes plan steps.";
|
|
572
657
|
if (this.plan?.steps.length)
|
|
573
658
|
this.setStep(0, "active");
|
|
574
659
|
const finished = await this.agentLoop(this.buildApiMessages(this.turnView(turnMessages), directive), token, approvalContext);
|
|
575
660
|
if (cancelled())
|
|
576
661
|
return;
|
|
577
662
|
if (this.plan) {
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
this.setStep(i, "done");
|
|
581
|
-
else
|
|
663
|
+
settlePlan(this.plan, currentTask()?.signal.aborted === true);
|
|
664
|
+
if (!finished && this.taskOutcome !== "partial" && this.plan.steps[0]?.status === "blocked")
|
|
582
665
|
this.setStep(0, "failed");
|
|
583
666
|
}
|
|
584
667
|
}
|
|
@@ -660,10 +743,11 @@ export class WebSession {
|
|
|
660
743
|
return null;
|
|
661
744
|
}
|
|
662
745
|
finally {
|
|
663
|
-
this.
|
|
664
|
-
|
|
665
|
-
|
|
746
|
+
if (token === this.turnToken) {
|
|
747
|
+
this.planning = false;
|
|
748
|
+
this.retrying = null;
|
|
666
749
|
this.publishLive();
|
|
750
|
+
}
|
|
667
751
|
}
|
|
668
752
|
}
|
|
669
753
|
/** Ends a turn whose floating promise rejected, instead of crashing the app. */
|
|
@@ -686,9 +770,10 @@ export class WebSession {
|
|
|
686
770
|
buildApiMessages(messages = this.messages, directive = "") {
|
|
687
771
|
// A step directive extends the system prompt: the plan is an instruction, not
|
|
688
772
|
// something the user just said.
|
|
689
|
-
const prompt = this.mode === "pro" ? loadProPrompt("main_model") :
|
|
773
|
+
const prompt = this.mode === "pro" ? loadProPrompt("main_model") : loadAgentPrompt(this.activeAgentMode);
|
|
690
774
|
const api = [
|
|
691
|
-
{ role: "system", content: directive ? `${prompt}\n\n${directive}` : prompt
|
|
775
|
+
{ role: "system", content: (directive ? `${prompt}\n\n${directive}` : prompt) +
|
|
776
|
+
(currentTask() ? `\n\n${currentTask().runtime.instructions()}` : "") },
|
|
692
777
|
];
|
|
693
778
|
for (const m of modelHistory(messages, this.context)) {
|
|
694
779
|
if (m.role === "user" || m.role === "assistant") {
|
|
@@ -706,8 +791,12 @@ export class WebSession {
|
|
|
706
791
|
* the caller owns the turn's end because a planned turn runs this per step.
|
|
707
792
|
*/
|
|
708
793
|
async agentLoop(apiMessages, token, approvalContext) {
|
|
709
|
-
const tools = this.mode === "pro" ? getProToolDefinitions() :
|
|
794
|
+
const tools = this.mode === "pro" ? [...getProToolDefinitions(), FINISH_STEP_DEFINITION] : [
|
|
795
|
+
...getToolDefinitions(),
|
|
796
|
+
...(this.activeAgentMode === "ptc" ? [(await import("../llm/codemode.js")).getCodeToolDefinition()] : []),
|
|
797
|
+
];
|
|
710
798
|
const team = this.proTeam;
|
|
799
|
+
let emptyReplies = 0;
|
|
711
800
|
try {
|
|
712
801
|
while (true) {
|
|
713
802
|
if (token !== this.turnToken || this.paused)
|
|
@@ -715,11 +804,19 @@ export class WebSession {
|
|
|
715
804
|
// Match the terminal: long tasks can keep using tools until the model
|
|
716
805
|
// finishes, while older tool results are trimmed near the context limit.
|
|
717
806
|
apiMessages = this.trimApiMessages(apiMessages);
|
|
807
|
+
if (currentTask())
|
|
808
|
+
await prepareContext({
|
|
809
|
+
messages: apiMessages, tools, capacity: team?.contextWindow("main_model") ?? modelContextWindow(this.provider, this.model, this.mode),
|
|
810
|
+
createClient: () => team?.client("main_model") ?? new LLMClient(this.model, this.provider, this.intensity),
|
|
811
|
+
stream: { signal: currentTask().runtime.signal, cancelled: () => token !== this.turnToken || this.paused },
|
|
812
|
+
onUsage: usage => { this.tokens.prompt += usage.prompt; this.tokens.completion += usage.completion; },
|
|
813
|
+
});
|
|
718
814
|
const client = team ? team.client("main_model") : new LLMClient(this.model, this.provider, this.intensity);
|
|
719
815
|
let text = "";
|
|
720
816
|
this.streamingContent = "";
|
|
721
817
|
this.publishLive();
|
|
722
818
|
for await (const chunk of client.streamTurn(apiMessages, tools, {
|
|
819
|
+
maxOutputTokens: outputAllowance(team?.contextWindow("main_model") ?? modelContextWindow(this.provider, this.model, this.mode)),
|
|
723
820
|
onRetry: (notice) => {
|
|
724
821
|
this.retrying = notice;
|
|
725
822
|
this.publishLive();
|
|
@@ -742,6 +839,30 @@ export class WebSession {
|
|
|
742
839
|
this.tokens.completion += client.usage.completion;
|
|
743
840
|
this.lastPromptTokens = client.usage.prompt;
|
|
744
841
|
}
|
|
842
|
+
if (currentTask()?.signal.aborted || client.outcome?.status === "cancelled") {
|
|
843
|
+
this.taskOutcome = "cancelled";
|
|
844
|
+
this.streamingContent = null;
|
|
845
|
+
this.messages.push({ role: "assistant", content: `[Task stopped: ${String(currentTask()?.signal.reason ?? "Task cancelled")}]\n${currentTask()?.runtime.partialReport() ?? ""}` });
|
|
846
|
+
return false;
|
|
847
|
+
}
|
|
848
|
+
if (client.outcome?.error?.code.endsWith("_budget") && currentTask()) {
|
|
849
|
+
const report = await finalTaskReport(currentTask().runtime, () => team?.client("main_model") ?? new LLMClient(this.model, this.provider, this.intensity), usage => { this.tokens.prompt += usage.prompt; this.tokens.completion += usage.completion; }, apiMessages);
|
|
850
|
+
if (token !== this.turnToken || this.paused)
|
|
851
|
+
return false;
|
|
852
|
+
this.taskOutcome = "partial";
|
|
853
|
+
this.streamingContent = null;
|
|
854
|
+
this.messages.push({ role: "assistant", content: report });
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
if (client.outcome?.status === "failed") {
|
|
858
|
+
const recorded = currentTask()?.runtime.evidence.length ? currentTask().runtime.partialReport() : "";
|
|
859
|
+
const message = client.outcome.error?.message ?? "Model request failed";
|
|
860
|
+
this.taskOutcome = recorded ? "partial" : "failed";
|
|
861
|
+
this.streamingContent = null;
|
|
862
|
+
this.messages.push({ role: "assistant", content: text +
|
|
863
|
+
(text.includes(message) ? "" : `\n[Error: ${message}]`) + (recorded ? `\n\n${recorded}` : "") });
|
|
864
|
+
return false;
|
|
865
|
+
}
|
|
745
866
|
const toolCalls = client.toolCalls;
|
|
746
867
|
if (text.trim()) {
|
|
747
868
|
this.messages.push({ role: "assistant", content: text });
|
|
@@ -770,11 +891,17 @@ export class WebSession {
|
|
|
770
891
|
this.save();
|
|
771
892
|
this.publishLive();
|
|
772
893
|
if (toolCalls.length === 0) {
|
|
773
|
-
if (
|
|
894
|
+
if (!text.trim() && emptyReplies++ === 0)
|
|
895
|
+
continue;
|
|
896
|
+
if (!text.trim()) {
|
|
897
|
+
this.taskOutcome = "failed";
|
|
774
898
|
this.messages.push({ role: "assistant", content: "[Error: Pro main model returned an empty completion.]" });
|
|
775
899
|
return false;
|
|
776
900
|
}
|
|
777
|
-
|
|
901
|
+
const success = client.outcome ? client.outcome.status === "completed" : !/\[Error:/.test(text);
|
|
902
|
+
if (!success)
|
|
903
|
+
this.taskOutcome = "failed";
|
|
904
|
+
return success;
|
|
778
905
|
}
|
|
779
906
|
for (const call of toolCalls) {
|
|
780
907
|
if (token !== this.turnToken || this.paused)
|
|
@@ -804,6 +931,7 @@ export class WebSession {
|
|
|
804
931
|
describeApprovalIntent(approvalContext) +
|
|
805
932
|
"Do not retry this call unchanged; ask the user how to proceed.";
|
|
806
933
|
toolMsg.result = denied;
|
|
934
|
+
currentTask()?.runtime.recordTool(call.name, args, denied, "denied");
|
|
807
935
|
apiMessages.push({ role: "tool", content: denied, tool_call_id: call.id });
|
|
808
936
|
this.save();
|
|
809
937
|
this.publishLive();
|
|
@@ -814,6 +942,13 @@ export class WebSession {
|
|
|
814
942
|
result = await this.runTool(call.name, args, toolMsg, token, apiMessages, approvalContext);
|
|
815
943
|
}
|
|
816
944
|
catch (err) {
|
|
945
|
+
if (err instanceof TaskControlError && err.code.endsWith("_budget")) {
|
|
946
|
+
toolMsg.result = `[Not executed: ${describeError(err)}]`;
|
|
947
|
+
this.save();
|
|
948
|
+
throw err;
|
|
949
|
+
}
|
|
950
|
+
if (token === this.turnToken)
|
|
951
|
+
this.taskOutcome = "failed";
|
|
817
952
|
result = `Error: ${err instanceof Error ? err.message : String(err)}`;
|
|
818
953
|
}
|
|
819
954
|
if (this.disposed)
|
|
@@ -822,7 +957,8 @@ export class WebSession {
|
|
|
822
957
|
this.save();
|
|
823
958
|
if (token !== this.turnToken || this.paused)
|
|
824
959
|
return false;
|
|
825
|
-
apiMessages.push({ role: "tool", content:
|
|
960
|
+
apiMessages.push({ role: "tool", content: toolMsg.evidenceId
|
|
961
|
+
? `${result}\n[Task evidence: ${toolMsg.evidenceId}; status=${toolMsg.executionStatus}]` : result, tool_call_id: call.id });
|
|
826
962
|
this.publishLive();
|
|
827
963
|
}
|
|
828
964
|
this.save();
|
|
@@ -831,6 +967,15 @@ export class WebSession {
|
|
|
831
967
|
catch (err) {
|
|
832
968
|
if (token !== this.turnToken)
|
|
833
969
|
return false;
|
|
970
|
+
if (err instanceof TaskControlError && err.code.endsWith("_budget") && currentTask()) {
|
|
971
|
+
const report = await finalTaskReport(currentTask().runtime, () => team?.client("main_model") ?? new LLMClient(this.model, this.provider, this.intensity), usage => { this.tokens.prompt += usage.prompt; this.tokens.completion += usage.completion; }, apiMessages);
|
|
972
|
+
if (token !== this.turnToken || this.paused)
|
|
973
|
+
return false;
|
|
974
|
+
this.taskOutcome = "partial";
|
|
975
|
+
this.streamingContent = null;
|
|
976
|
+
this.messages.push({ role: "assistant", content: report });
|
|
977
|
+
return false;
|
|
978
|
+
}
|
|
834
979
|
if (this.streamingContent) {
|
|
835
980
|
this.messages.push({ role: "assistant", content: this.streamingContent });
|
|
836
981
|
}
|
|
@@ -856,15 +1001,26 @@ export class WebSession {
|
|
|
856
1001
|
}
|
|
857
1002
|
// ---- approval gate ---------------------------------------------------
|
|
858
1003
|
/** Classify a tool call and, when it is risky, wait for a decision. */
|
|
859
|
-
async requestApproval(tool, args, context = captureApprovalContext(this.messages)) {
|
|
1004
|
+
async requestApproval(tool, args, context = captureApprovalContext(this.messages), signal = currentTask()?.runtime.signal ?? this.turnAbort?.signal) {
|
|
1005
|
+
const linked = linkSignals(signal, currentTask()?.signal);
|
|
1006
|
+
signal = linked.signal;
|
|
1007
|
+
try {
|
|
1008
|
+
currentTask()?.runtime.assertTool(tool, args);
|
|
1009
|
+
}
|
|
1010
|
+
catch (error) {
|
|
1011
|
+
linked.dispose();
|
|
1012
|
+
return { approved: false, source: "input", reason: error instanceof Error ? error.message : String(error) };
|
|
1013
|
+
}
|
|
860
1014
|
const risk = assessToolCall(tool, args, this.dir);
|
|
861
|
-
if (!risk)
|
|
1015
|
+
if (!risk) {
|
|
1016
|
+
linked.dispose();
|
|
862
1017
|
return { approved: true, source: "prompt", reason: "routine call" };
|
|
1018
|
+
}
|
|
863
1019
|
const approvalToken = this.turnToken;
|
|
864
1020
|
const team = this.proTeam;
|
|
865
1021
|
const gate = new ApprovalGate({
|
|
866
1022
|
askHuman: () => new Promise((resolve) => {
|
|
867
|
-
if (
|
|
1023
|
+
if (approvalToken !== this.turnToken || this.paused || this.disposed || signal?.aborted === true) {
|
|
868
1024
|
resolve(false);
|
|
869
1025
|
return;
|
|
870
1026
|
}
|
|
@@ -875,12 +1031,12 @@ export class WebSession {
|
|
|
875
1031
|
...(this.mode === "pro" && team ? {
|
|
876
1032
|
createReviewerClient: () => team.client("confirm_model"),
|
|
877
1033
|
reviewerSystemPrompt: () => loadProPrompt("confirm_model"),
|
|
878
|
-
cancelled: () => approvalToken !== this.turnToken || this.paused || this.disposed,
|
|
879
1034
|
} : {}),
|
|
1035
|
+
cancelled: () => approvalToken !== this.turnToken || this.paused || this.disposed || signal?.aborted === true,
|
|
880
1036
|
autoApprovalEnabled: () => this.approvalMode === "auto",
|
|
881
1037
|
extremeAutoApprovalEnabled: () => loadConfig(this.mode).auto_approve_extreme_risk === true,
|
|
882
1038
|
onReview: (info) => {
|
|
883
|
-
if (
|
|
1039
|
+
if (approvalToken !== this.turnToken || this.paused || this.disposed || signal?.aborted === true)
|
|
884
1040
|
return;
|
|
885
1041
|
this.reviewing = info
|
|
886
1042
|
? {
|
|
@@ -891,7 +1047,22 @@ export class WebSession {
|
|
|
891
1047
|
this.publishLive();
|
|
892
1048
|
},
|
|
893
1049
|
});
|
|
894
|
-
|
|
1050
|
+
const abortApproval = () => {
|
|
1051
|
+
const pending = this.pendingApproval;
|
|
1052
|
+
if (pending?.args !== args)
|
|
1053
|
+
return;
|
|
1054
|
+
this.pendingApproval = null;
|
|
1055
|
+
pending.resolve(false);
|
|
1056
|
+
this.publishLive();
|
|
1057
|
+
};
|
|
1058
|
+
signal.addEventListener("abort", abortApproval, { once: true });
|
|
1059
|
+
try {
|
|
1060
|
+
return await gate.decide({ tool, args, cwd: this.dir, ...context, risk });
|
|
1061
|
+
}
|
|
1062
|
+
finally {
|
|
1063
|
+
signal.removeEventListener("abort", abortApproval);
|
|
1064
|
+
linked.dispose();
|
|
1065
|
+
}
|
|
895
1066
|
}
|
|
896
1067
|
/** Reviewer model: `approval_model` when set, otherwise the chat model. */
|
|
897
1068
|
reviewerModel() {
|
|
@@ -905,63 +1076,172 @@ export class WebSession {
|
|
|
905
1076
|
}
|
|
906
1077
|
return { model: this.model, provider: this.provider, intensity: this.intensity };
|
|
907
1078
|
}
|
|
1079
|
+
async runProgram(args, token, apiMessages, approvalContext) {
|
|
1080
|
+
if (this.activeAgentMode !== "ptc")
|
|
1081
|
+
return "[Error: execute_ts requires PTC mode]";
|
|
1082
|
+
const { executeCodeProgram, ProgramToolDenied } = await import("../llm/codemode.js");
|
|
1083
|
+
const signal = currentTask()?.runtime.signal ?? this.turnAbort?.signal;
|
|
1084
|
+
const cancelled = () => token !== this.turnToken || this.paused || signal?.aborted;
|
|
1085
|
+
const result = await executeCodeProgram(args, {
|
|
1086
|
+
signal,
|
|
1087
|
+
onPlan: plan => {
|
|
1088
|
+
if (cancelled())
|
|
1089
|
+
return;
|
|
1090
|
+
this.plan = plan;
|
|
1091
|
+
if (currentTask())
|
|
1092
|
+
currentTask().runtime.plan = plan;
|
|
1093
|
+
this.save();
|
|
1094
|
+
this.publishLive();
|
|
1095
|
+
},
|
|
1096
|
+
onStep: (index, status) => { if (!cancelled())
|
|
1097
|
+
this.setStep(index, status); },
|
|
1098
|
+
call: async (name, callArgs, programSignal) => {
|
|
1099
|
+
if (cancelled() || programSignal?.aborted)
|
|
1100
|
+
throw new Error("Execution cancelled");
|
|
1101
|
+
const message = { role: "tool", name, arguments: callArgs, result: "…" };
|
|
1102
|
+
this.messages.push(message);
|
|
1103
|
+
this.save();
|
|
1104
|
+
this.publishLive();
|
|
1105
|
+
const stopApproval = () => {
|
|
1106
|
+
if (this.pendingApproval?.args !== callArgs)
|
|
1107
|
+
return;
|
|
1108
|
+
const pending = this.pendingApproval;
|
|
1109
|
+
this.pendingApproval = null;
|
|
1110
|
+
pending.resolve(false);
|
|
1111
|
+
this.publishLive();
|
|
1112
|
+
};
|
|
1113
|
+
programSignal?.addEventListener("abort", stopApproval, { once: true });
|
|
1114
|
+
try {
|
|
1115
|
+
const decision = await this.requestApproval(name, callArgs, {
|
|
1116
|
+
...approvalContext, context: captureApprovalContext(apiMessages).context,
|
|
1117
|
+
}, programSignal);
|
|
1118
|
+
if (cancelled() || programSignal?.aborted)
|
|
1119
|
+
throw new Error("Execution cancelled");
|
|
1120
|
+
if (!decision.approved) {
|
|
1121
|
+
message.result = `[Not executed: ${decision.reason}]`;
|
|
1122
|
+
throw new ProgramToolDenied(`Approval denied: ${decision.reason}. Do not retry or work around this denial.`);
|
|
1123
|
+
}
|
|
1124
|
+
const value = await this.runTool(name, callArgs, message, token, apiMessages, approvalContext, programSignal);
|
|
1125
|
+
message.result = value;
|
|
1126
|
+
if (cancelled() || programSignal?.aborted)
|
|
1127
|
+
throw new Error("Execution cancelled");
|
|
1128
|
+
return value;
|
|
1129
|
+
}
|
|
1130
|
+
catch (error) {
|
|
1131
|
+
if (message.result === "…")
|
|
1132
|
+
message.result = `[Error: ${error instanceof Error ? error.message : String(error)}]`;
|
|
1133
|
+
throw error;
|
|
1134
|
+
}
|
|
1135
|
+
finally {
|
|
1136
|
+
programSignal?.removeEventListener("abort", stopApproval);
|
|
1137
|
+
if (this.messages.includes(message)) {
|
|
1138
|
+
this.save();
|
|
1139
|
+
this.publishLive();
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
},
|
|
1143
|
+
});
|
|
1144
|
+
if (!cancelled()) {
|
|
1145
|
+
this.save();
|
|
1146
|
+
this.publishLive();
|
|
1147
|
+
}
|
|
1148
|
+
return result;
|
|
1149
|
+
}
|
|
908
1150
|
// ---- sub-agents ------------------------------------------------------
|
|
909
1151
|
/**
|
|
910
1152
|
* Run one approved tool call. `delegate` runs a child agent instead of
|
|
911
1153
|
* touching the filesystem, so it is handled here, where the session's model,
|
|
912
1154
|
* approval gate and abort signal live.
|
|
913
1155
|
*/
|
|
914
|
-
async runTool(name, args, toolMsg, token, apiMessages, approvalContext) {
|
|
1156
|
+
async runTool(name, args, toolMsg, token, apiMessages, approvalContext, executionSignal) {
|
|
1157
|
+
const runtime = currentTask()?.runtime;
|
|
1158
|
+
const execute = () => this.dispatchTool(name, args, toolMsg, token, apiMessages, approvalContext, executionSignal);
|
|
1159
|
+
if (!runtime)
|
|
1160
|
+
return execute();
|
|
1161
|
+
const result = await runtime.tool(name, args, execute);
|
|
1162
|
+
const evidence = runtime.evidence.at(-1);
|
|
1163
|
+
if (evidence && this.mode === "pro") {
|
|
1164
|
+
toolMsg.evidenceId = evidence.id;
|
|
1165
|
+
toolMsg.executionStatus = evidence.status;
|
|
1166
|
+
}
|
|
1167
|
+
return result;
|
|
1168
|
+
}
|
|
1169
|
+
async dispatchTool(name, args, toolMsg, token, apiMessages, approvalContext, signal = currentTask()?.runtime.signal ?? this.turnAbort?.signal) {
|
|
1170
|
+
if (name === FINISH_STEP_TOOL && currentTask()) {
|
|
1171
|
+
const result = finishPlanStep(this.plan, args, currentTask().runtime);
|
|
1172
|
+
this.save();
|
|
1173
|
+
this.publishLive();
|
|
1174
|
+
return result;
|
|
1175
|
+
}
|
|
915
1176
|
if (this.mode === "pro" && (name === "call_pro_agent" || name === DELEGATE_TOOL)) {
|
|
916
1177
|
const team = this.proTeam;
|
|
917
1178
|
if (!team)
|
|
918
1179
|
return "[Error: Pro models have not passed preflight]";
|
|
919
1180
|
return team.call(String(args.role ?? "backend_output_model"), String(args.task ?? ""), `${typeof args.context === "string" ? args.context : ""}\n\nCurrent user request: ${approvalContext.prompt}\n` +
|
|
920
|
-
captureApprovalContext(apiMessages).context, 1);
|
|
1181
|
+
captureApprovalContext(apiMessages).context, 1, args.read_only === true, Array.isArray(args.write_paths) ? args.write_paths.filter((p) => typeof p === "string") : undefined);
|
|
921
1182
|
}
|
|
1183
|
+
if (name === "execute_ts")
|
|
1184
|
+
return this.runProgram(args, token, apiMessages, approvalContext);
|
|
922
1185
|
if (name !== DELEGATE_TOOL)
|
|
923
|
-
return executeTool(name, args, this.dir);
|
|
924
|
-
return this.delegate(args, toolMsg, token, apiMessages, approvalContext);
|
|
1186
|
+
return executeTool(name, args, this.dir, signal);
|
|
1187
|
+
return this.delegate(args, toolMsg, token, apiMessages, approvalContext, signal);
|
|
925
1188
|
}
|
|
926
1189
|
/**
|
|
927
1190
|
* Run a sub-agent for one `delegate` call and return its report. The child
|
|
928
1191
|
* works in its own transcript; only the report reaches this conversation, and
|
|
929
1192
|
* its risky calls go through the same approval gate as the parent turn.
|
|
930
1193
|
*/
|
|
931
|
-
async delegate(args, toolMsg, token, apiMessages, approvalContext) {
|
|
1194
|
+
async delegate(args, toolMsg, token, apiMessages, approvalContext, executionSignal) {
|
|
932
1195
|
const task = String(args.task ?? "").trim();
|
|
933
1196
|
if (!task)
|
|
934
1197
|
return "[Error: delegate needs a task: what should the sub-agent do?]";
|
|
935
1198
|
const context = typeof args.context === "string" ? args.context.trim() : "";
|
|
936
1199
|
const approval = { ...approvalContext, context: captureApprovalContext(apiMessages).context };
|
|
1200
|
+
const maxRounds = subAgentRounds(loadConfig(this.mode).subagent_max_rounds);
|
|
1201
|
+
const runtime = currentTask()?.runtime;
|
|
1202
|
+
const signal = executionSignal ?? runtime?.signal ?? this.turnAbort?.signal;
|
|
1203
|
+
const stopped = () => token !== this.turnToken || this.paused || signal?.aborted === true;
|
|
937
1204
|
const run = await runSubAgentTask({
|
|
1205
|
+
runtime, signal,
|
|
1206
|
+
readOnly: args.read_only === true,
|
|
1207
|
+
writePaths: Array.isArray(args.write_paths) ? args.write_paths.filter((p) => typeof p === "string") : undefined,
|
|
1208
|
+
contextWindow: modelContextWindow(this.provider, this.model, this.mode),
|
|
938
1209
|
createClient: () => new LLMClient(this.model, this.provider, this.intensity),
|
|
939
1210
|
system: loadSubAgentPrompt(),
|
|
940
|
-
task: buildSubAgentTask({ task, context, cwd: this.dir }),
|
|
1211
|
+
task: buildSubAgentTask({ task, context, cwd: this.dir, rounds: maxRounds }),
|
|
941
1212
|
tools: SUB_AGENT_TOOLS,
|
|
942
|
-
|
|
1213
|
+
maxRounds,
|
|
1214
|
+
approve: (name, callArgs) => this.requestApproval(name, callArgs, approval, signal),
|
|
943
1215
|
execute: (name, callArgs) => name === DELEGATE_TOOL
|
|
944
1216
|
? Promise.resolve("[Error: sub-agents cannot delegate further]")
|
|
945
|
-
: executeTool(name, callArgs, this.dir),
|
|
1217
|
+
: executeTool(name, callArgs, this.dir, signal),
|
|
946
1218
|
onProgress: (label) => {
|
|
1219
|
+
if (stopped())
|
|
1220
|
+
return;
|
|
947
1221
|
this.subagent = label;
|
|
948
1222
|
toolMsg.result = `… ${label}`;
|
|
949
1223
|
this.publishLive();
|
|
950
1224
|
},
|
|
951
1225
|
onUsage: (usage) => {
|
|
1226
|
+
if (stopped())
|
|
1227
|
+
return;
|
|
952
1228
|
this.tokens.prompt += usage.prompt;
|
|
953
1229
|
this.tokens.completion += usage.completion;
|
|
954
1230
|
},
|
|
955
1231
|
onRetry: (notice) => {
|
|
1232
|
+
if (stopped())
|
|
1233
|
+
return;
|
|
956
1234
|
this.retrying = notice;
|
|
957
1235
|
this.publishLive();
|
|
958
1236
|
},
|
|
959
|
-
cancelled:
|
|
1237
|
+
cancelled: stopped,
|
|
960
1238
|
});
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
1239
|
+
if (!stopped()) {
|
|
1240
|
+
this.subagent = null;
|
|
1241
|
+
this.retrying = null;
|
|
1242
|
+
toolMsg.result = TOOL_PENDING;
|
|
1243
|
+
this.publishLive();
|
|
1244
|
+
}
|
|
965
1245
|
return formatSubAgentResult(run);
|
|
966
1246
|
}
|
|
967
1247
|
// ---- compression -----------------------------------------------------
|
|
@@ -1067,7 +1347,7 @@ export class WebSession {
|
|
|
1067
1347
|
if (chat.length === 0)
|
|
1068
1348
|
return true;
|
|
1069
1349
|
try {
|
|
1070
|
-
this.sessionId = saveSession(chat, this.model.name, this.provider, this.intensity, this.dir, this.sessionId, this.tokens, this.context, this.approvalMode, this.plan, this.mode, this.proPreset ?? undefined);
|
|
1350
|
+
this.sessionId = saveSession(chat, this.model.name, this.provider, this.intensity, this.dir, this.sessionId, this.tokens, this.context, this.approvalMode, this.plan, this.mode, this.proPreset ?? undefined, this.agentMode, this.taskRuntime?.snapshot() ?? this.lastTask);
|
|
1071
1351
|
this.saveError = null;
|
|
1072
1352
|
return true;
|
|
1073
1353
|
}
|