@pentoshi/clai 3.8.46 → 3.8.47
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/agent/compaction-summary.js +1 -1
- package/dist/agent/compaction-summary.js.map +1 -1
- package/dist/agent/plan-tool.js +110 -14
- package/dist/agent/plan-tool.js.map +1 -1
- package/dist/agent/reliability-policy.d.ts +4 -3
- package/dist/agent/reliability-policy.js +4 -3
- package/dist/agent/reliability-policy.js.map +1 -1
- package/dist/agent/responder-context.d.ts +10 -0
- package/dist/agent/responder-context.js +50 -0
- package/dist/agent/responder-context.js.map +1 -0
- package/dist/agent/runner.js +160 -12
- package/dist/agent/runner.js.map +1 -1
- package/dist/agent/task-plan.d.ts +4 -0
- package/dist/agent/task-plan.js.map +1 -1
- package/dist/app/adapters/current-jobs-adapter.js +7 -0
- package/dist/app/adapters/current-jobs-adapter.js.map +1 -1
- package/dist/app/controllers/job-controller.d.ts +9 -6
- package/dist/app/controllers/job-controller.js +21 -0
- package/dist/app/controllers/job-controller.js.map +1 -1
- package/dist/app/controllers/session-controller.d.ts +14 -33
- package/dist/app/controllers/session-controller.js +71 -132
- package/dist/app/controllers/session-controller.js.map +1 -1
- package/dist/app/controllers/session-prompt-queue.d.ts +34 -0
- package/dist/app/controllers/session-prompt-queue.js +141 -0
- package/dist/app/controllers/session-prompt-queue.js.map +1 -0
- package/dist/app/controllers/session-responder.d.ts +49 -0
- package/dist/app/controllers/session-responder.js +311 -0
- package/dist/app/controllers/session-responder.js.map +1 -0
- package/dist/app/ports/jobs-port.d.ts +10 -7
- package/dist/prompts/embedded.js +1 -1
- package/dist/prompts/embedded.js.map +1 -1
- package/dist/prompts/system.agent.md +14 -9
- package/dist/repl.js +24 -14
- package/dist/repl.js.map +1 -1
- package/dist/safety/engagement-policy.js +39 -7
- package/dist/safety/engagement-policy.js.map +1 -1
- package/dist/store/config.js +1 -1
- package/dist/store/config.js.map +1 -1
- package/dist/store/plan.d.ts +11 -0
- package/dist/store/plan.js +66 -3
- package/dist/store/plan.js.map +1 -1
- package/dist/tools/definitions.js +35 -4
- package/dist/tools/definitions.js.map +1 -1
- package/dist/tools/jobs.d.ts +72 -3
- package/dist/tools/jobs.js +743 -121
- package/dist/tools/jobs.js.map +1 -1
- package/dist/tools/pentest-workflows.js +80 -4
- package/dist/tools/pentest-workflows.js.map +1 -1
- package/dist/tools/registry.js +60 -19
- package/dist/tools/registry.js.map +1 -1
- package/dist/tools/tool-types.d.ts +5 -0
- package/dist/tools/wordlists.js +74 -87
- package/dist/tools/wordlists.js.map +1 -1
- package/dist/tui-v2/actions/action-id.js +1 -1
- package/dist/tui-v2/actions/action-id.js.map +1 -1
- package/dist/tui-v2/actions/keymap.js +1 -1
- package/dist/tui-v2/actions/keymap.js.map +1 -1
- package/dist/tui-v2/app/App.js +82 -12
- package/dist/tui-v2/app/App.js.map +1 -1
- package/dist/tui-v2/app/commands/session-commands.js +0 -5
- package/dist/tui-v2/app/commands/session-commands.js.map +1 -1
- package/dist/tui-v2/bootstrap/composition-root.js +2 -0
- package/dist/tui-v2/bootstrap/composition-root.js.map +1 -1
- package/dist/tui-v2/components/jobs/jobs-panel.d.ts +12 -0
- package/dist/tui-v2/components/jobs/jobs-panel.js +184 -14
- package/dist/tui-v2/components/jobs/jobs-panel.js.map +1 -1
- package/dist/tui-v2/components/plan/plan-view.js +7 -3
- package/dist/tui-v2/components/plan/plan-view.js.map +1 -1
- package/dist/tui-v2/components/status/status-line.js +2 -3
- package/dist/tui-v2/components/status/status-line.js.map +1 -1
- package/dist/tui-v2/components/toast/toast-host.js +16 -1
- package/dist/tui-v2/components/toast/toast-host.js.map +1 -1
- package/dist/tui-v2/components/transcript/tool-card.d.ts +3 -0
- package/dist/tui-v2/components/transcript/tool-card.js +12 -2
- package/dist/tui-v2/components/transcript/tool-card.js.map +1 -1
- package/dist/tui-v2/components/transcript/transcript-row.js +1 -1
- package/dist/tui-v2/components/transcript/transcript-row.js.map +1 -1
- package/dist/tui-v2/composer/composer-editor.d.ts +6 -0
- package/dist/tui-v2/composer/composer-editor.js +10 -17
- package/dist/tui-v2/composer/composer-editor.js.map +1 -1
- package/dist/tui-v2/composer/composer-image-paste.d.ts +1 -0
- package/dist/tui-v2/composer/composer-image-paste.js +22 -1
- package/dist/tui-v2/composer/composer-image-paste.js.map +1 -1
- package/dist/tui-v2/state/transcript-store.d.ts +4 -0
- package/dist/tui-v2/state/transcript-store.js +13 -0
- package/dist/tui-v2/state/transcript-store.js.map +1 -1
- package/dist/ui/intro-card.js +3 -3
- package/dist/ui/intro-card.js.map +1 -1
- package/dist/ui/mentions.d.ts +11 -13
- package/dist/ui/mentions.js +67 -26
- package/dist/ui/mentions.js.map +1 -1
- package/dist/ui/plan-pane.js +6 -2
- package/dist/ui/plan-pane.js.map +1 -1
- package/dist/version.generated.d.ts +2 -2
- package/dist/version.generated.js +2 -2
- package/package.json +1 -1
package/dist/agent/runner.js
CHANGED
|
@@ -6,11 +6,34 @@ import { syntheticToolCallId, isTextOnlyModel, fromWireName, } from "../llm/tool
|
|
|
6
6
|
import { sanitizeAssistantText } from "../ui/ansi-box.js";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { jobManager } from "../tools/jobs.js";
|
|
9
|
+
import { responderContextMessage, upsertResponderContextMessage, } from "./responder-context.js";
|
|
9
10
|
import { agentModeDirective, planModeDirective, renderAgentSystemPrompt, renderCompactAgentSystemPrompt, scratchDirFor, toolNudge, } from "../prompts/index.js";
|
|
10
11
|
import { getConfig } from "../store/config.js";
|
|
11
12
|
import { beginSessionWorkspace, getActiveSessionWorkspace, } from "../store/session-workspace.js";
|
|
12
13
|
import { groqInputTokenBudget } from "../llm/groq.js";
|
|
13
14
|
import { classifyToolCall, isPentestToolCall, scopeHint, scopeTargetForToolCall, } from "../safety/classifier.js";
|
|
15
|
+
/**
|
|
16
|
+
* Scope/engagement classification runs on every tool call and parses
|
|
17
|
+
* model-supplied arguments (URLs, hosts, commands). A malformed argument must
|
|
18
|
+
* never throw out of classification and abort the whole turn — degrade to
|
|
19
|
+
* "no target / no action" so the normal permission path still applies.
|
|
20
|
+
*/
|
|
21
|
+
function safeScopeTargetForToolCall(call) {
|
|
22
|
+
try {
|
|
23
|
+
return scopeTargetForToolCall(call);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
return undefined;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function safeEngagementActionForToolCall(call) {
|
|
30
|
+
try {
|
|
31
|
+
return engagementActionForToolCall(call);
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
return undefined;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
14
37
|
import { availableToolNames, normalizeToolCall, runToolCall, BATCH_SAFE_TOOLS, } from "../tools/registry.js";
|
|
15
38
|
import { getToolDefinitions, getCompactToolDefinitions, PLAN_TOOL_NAMES, } from "../tools/definitions.js";
|
|
16
39
|
import { appendAssistantWithTools, ensureUniqueToolCallIds, appendToolResult, assertValidToolProtocol, fillMissingToolResults, repairToolProtocol, } from "./tool-history.js";
|
|
@@ -32,7 +55,7 @@ import { isScratchOnlyWrite } from "./scratch-write.js";
|
|
|
32
55
|
import { COMPACTION_SYSTEM_PROMPT, } from "./compaction-summary.js";
|
|
33
56
|
import { maybeAppendPlanModeReminder, PLAN_REMINDER_TOAST, } from "./plan-mode-reminders.js";
|
|
34
57
|
import { LoopGuard } from "./loop-guard.js";
|
|
35
|
-
import { loadPlan, savePlan, markTask, readyPlanTasks, isPlanTerminal, isPlanSuccessful, } from "../store/plan.js";
|
|
58
|
+
import { loadPlan, savePlan, markTask, appendPlanTask, readyPlanTasks, isPlanTerminal, isPlanSuccessful, } from "../store/plan.js";
|
|
36
59
|
import { fsWrite, isOutsideWorkingDirectory, resolveFsToolPath, } from "../tools/fs.js";
|
|
37
60
|
import { stripSentinelTokens, parseToolCall, recognizeBareToolJson, looksLikeTruncatedToolCall, salvageTruncatedWrite, salvageTruncatedWriteFromNative, countToolFences, parseAllToolCalls, groupToolCallsForExecution, buildTurnHistory, collapseRepeatedText, textBeforeToolCall, formatToolArgs, looksLikePentestTask, looksLikeBuildTask, looksLikeInformationalQuery, looksLikeIdleOrSocialPrompt, looksLikeActionNarration, looksLikeWebActionNarration, looksLikePlanNarration, looksLikeErrorDiagnosisWithFixIntent, localHttpProbeIsFailure, localHttpProbeIsSuccess, requiresFreshWebSearch, freshnessGuardMessage, buildWorkflowDirective, narrowNmapOperationDirective, pentestWorkflowDirective, pentestNoLocalServerDirective, shouldDimToolChatter, looksLikePromptLeak, } from "./tool-call-parser.js";
|
|
38
61
|
import { createSessionPolicy, isPreApprovalAllowedTool, isPlanModeAllowedShellCommand, isPlanModeAllowedTool, isPlanApprovedByStatus, planHasOpenWork, isAbortError, shouldEnableImageOcr, } from "./session-policy.js";
|
|
@@ -574,6 +597,20 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
574
597
|
userMessage,
|
|
575
598
|
];
|
|
576
599
|
liveMessages = messages;
|
|
600
|
+
const responderWakeTurn = prompt.startsWith("Responder completion ready (");
|
|
601
|
+
const refreshResponderInbox = () => {
|
|
602
|
+
const pending = responderWakeTurn
|
|
603
|
+
? jobManager
|
|
604
|
+
.getPendingNotifications(session.sessionId)
|
|
605
|
+
.filter((notification) => notification.responder && Boolean(notification.deliveredAt))
|
|
606
|
+
: [];
|
|
607
|
+
upsertResponderContextMessage(messages, responderContextMessage({
|
|
608
|
+
running: jobManager
|
|
609
|
+
.getRunningJobs(session.sessionId)
|
|
610
|
+
.filter((job) => job.responder),
|
|
611
|
+
pending,
|
|
612
|
+
}));
|
|
613
|
+
};
|
|
577
614
|
/** Assigned after session flags exist — see below. */
|
|
578
615
|
let refreshSessionState = () => undefined;
|
|
579
616
|
/** Model-only nudge — never shown as a YOU bubble / WARN in the chat UI. */
|
|
@@ -698,7 +735,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
698
735
|
};
|
|
699
736
|
const reconcileOpenTaskBeforeFinalizing = async () => {
|
|
700
737
|
const plan = await loadPlan(session.sessionId).catch(() => undefined);
|
|
701
|
-
const open = plan?.tasks.find((task) => task.state === "in_progress");
|
|
738
|
+
const open = plan?.tasks.find((task) => task.state === "in_progress" && !task.responderOwned);
|
|
702
739
|
if (!plan || !open)
|
|
703
740
|
return plan;
|
|
704
741
|
const gate = completionGateForTask(plan, open.id);
|
|
@@ -756,9 +793,9 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
756
793
|
const root = getActiveProjectRoot() ?? p?.meta?.projectRoot;
|
|
757
794
|
const pm = p?.meta?.packageManager ??
|
|
758
795
|
(root ? detectPackageManager(root) : undefined);
|
|
759
|
-
const open = p?.tasks.find((
|
|
796
|
+
const open = p?.tasks.find((task) => task.state === "in_progress" && !task.responderOwned);
|
|
760
797
|
const pending = p?.tasks
|
|
761
|
-
.filter((
|
|
798
|
+
.filter((task) => task.state === "pending" && !task.responderOwned)
|
|
762
799
|
.map((t) => `[${t.id}] ${t.title}`);
|
|
763
800
|
const done = p?.tasks
|
|
764
801
|
.filter((t) => t.state === "done" || t.state === "skipped")
|
|
@@ -967,7 +1004,9 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
967
1004
|
contextOutput: reason,
|
|
968
1005
|
};
|
|
969
1006
|
}
|
|
970
|
-
if (call.name === "plan.create" ||
|
|
1007
|
+
if (call.name === "plan.create" ||
|
|
1008
|
+
call.name === "task.add" ||
|
|
1009
|
+
call.name === "task.update") {
|
|
971
1010
|
// Evidence gate: refuse done until at least one successful work tool
|
|
972
1011
|
// ran under this task (model must see results and be satisfied).
|
|
973
1012
|
if (call.name === "task.update") {
|
|
@@ -1177,8 +1216,9 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1177
1216
|
if (session.planApproved.value) {
|
|
1178
1217
|
const livePlanForGate = await loadPlan(session.sessionId).catch(() => undefined);
|
|
1179
1218
|
if (livePlanForGate) {
|
|
1180
|
-
const unfinished = livePlanForGate.tasks.some((
|
|
1181
|
-
|
|
1219
|
+
const unfinished = livePlanForGate.tasks.some((task) => !task.responderOwned &&
|
|
1220
|
+
(task.state === "pending" || task.state === "in_progress"));
|
|
1221
|
+
const inProgress = livePlanForGate.tasks.find((task) => task.state === "in_progress" && !task.responderOwned);
|
|
1182
1222
|
if (unfinished && !inProgress) {
|
|
1183
1223
|
// tool.check / fs.list preflight: allow without auto-opening a task
|
|
1184
1224
|
// (auto-start on preflight made models skip task.update and confused scope).
|
|
@@ -1256,9 +1296,9 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1256
1296
|
writeToolCall(toolEventId, call, styleToolChatter(call, toolCallLine) + "\n");
|
|
1257
1297
|
alreadyPrintedIds.add(toolEventId);
|
|
1258
1298
|
}
|
|
1259
|
-
const scopeTarget =
|
|
1299
|
+
const scopeTarget = safeScopeTargetForToolCall(call);
|
|
1260
1300
|
const engagementAction = pentestSession || isPentestToolCall(call) || Boolean(scope)
|
|
1261
|
-
?
|
|
1301
|
+
? safeEngagementActionForToolCall(call)
|
|
1262
1302
|
: undefined;
|
|
1263
1303
|
const engagementDecision = engagementAction
|
|
1264
1304
|
? evaluateEngagementAction(scope, engagementAction)
|
|
@@ -1391,7 +1431,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1391
1431
|
}
|
|
1392
1432
|
parentSignal.throwIfAborted();
|
|
1393
1433
|
const planAtDispatch = await loadPlan(session.sessionId).catch(() => undefined);
|
|
1394
|
-
dispatchedTaskId = planAtDispatch?.tasks.find((task) => task.state === "in_progress")?.id;
|
|
1434
|
+
dispatchedTaskId = planAtDispatch?.tasks.find((task) => task.state === "in_progress" && !task.responderOwned)?.id;
|
|
1395
1435
|
if (!dispatchedTaskId && planAtDispatch?.kind === "pentest") {
|
|
1396
1436
|
const candidate = pickPendingTaskForToolCall(readyPlanTasks(planAtDispatch), call, planAtDispatch.tasks.map((task) => task.title));
|
|
1397
1437
|
dispatchedTaskId = candidate?.id;
|
|
@@ -1506,6 +1546,12 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1506
1546
|
confirmed: true,
|
|
1507
1547
|
userPrompt: prompt,
|
|
1508
1548
|
sessionId: session.sessionId,
|
|
1549
|
+
...(dispatchedTaskId ? { taskId: dispatchedTaskId } : {}),
|
|
1550
|
+
wakeOnCompletion: true,
|
|
1551
|
+
monitor: {
|
|
1552
|
+
toolName: call.name,
|
|
1553
|
+
toolEventId,
|
|
1554
|
+
},
|
|
1509
1555
|
...(engagementAction && scope
|
|
1510
1556
|
? {
|
|
1511
1557
|
engagementAuthorization: {
|
|
@@ -1739,6 +1785,102 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
1739
1785
|
writeNotice("info", `project root → ${fromScaffold}`, chalk.dim(` ℹ project root set to ${fromScaffold}\n`));
|
|
1740
1786
|
}
|
|
1741
1787
|
}
|
|
1788
|
+
if (result.backgroundJob) {
|
|
1789
|
+
const durableJob = jobManager.getJob(result.backgroundJob.id);
|
|
1790
|
+
// Responder linkage is opt-in: only jobs launched with responder:true
|
|
1791
|
+
// become fire-and-forget plan subtasks that auto-wake on completion.
|
|
1792
|
+
// Plain background jobs stay pollable (shell.jobs/shell.tail) as before.
|
|
1793
|
+
if (durableJob?.responder) {
|
|
1794
|
+
const livePlan = await loadPlan(session.sessionId).catch(() => undefined);
|
|
1795
|
+
let linkedTaskId = dispatchedTaskId;
|
|
1796
|
+
let linkedParentTaskId;
|
|
1797
|
+
let responderTaskId;
|
|
1798
|
+
if (durableJob && livePlan) {
|
|
1799
|
+
const existing = livePlan.tasks.find((task) => task.jobId === durableJob.id);
|
|
1800
|
+
const parentTaskId = dispatchedTaskId && livePlan.tasks.some((task) => task.id === dispatchedTaskId)
|
|
1801
|
+
? dispatchedTaskId
|
|
1802
|
+
: undefined;
|
|
1803
|
+
const terminalState = durableJob.status === "exited"
|
|
1804
|
+
? "done"
|
|
1805
|
+
: durableJob.status === "failed" ||
|
|
1806
|
+
durableJob.status === "killed" ||
|
|
1807
|
+
durableJob.status === "lost"
|
|
1808
|
+
? "failed"
|
|
1809
|
+
: "in_progress";
|
|
1810
|
+
const note = `job=${durableJob.id} pid=${durableJob.pid ?? "?"} status=${durableJob.status} ` +
|
|
1811
|
+
`artifact=${durableJob.stdoutArtifact}`;
|
|
1812
|
+
const responderTask = existing ?? appendPlanTask(livePlan, {
|
|
1813
|
+
title: `Responder · ${durableJob.name ?? durableJob.commandDisplay.slice(0, 96)}`,
|
|
1814
|
+
state: terminalState,
|
|
1815
|
+
note,
|
|
1816
|
+
dependencies: [],
|
|
1817
|
+
resourceLocks: [],
|
|
1818
|
+
parentTaskId,
|
|
1819
|
+
jobId: durableJob.id,
|
|
1820
|
+
processId: durableJob.pid,
|
|
1821
|
+
responderOwned: true,
|
|
1822
|
+
});
|
|
1823
|
+
responderTask.state = terminalState;
|
|
1824
|
+
responderTask.note = note;
|
|
1825
|
+
responderTask.jobId = durableJob.id;
|
|
1826
|
+
responderTask.processId = durableJob.pid;
|
|
1827
|
+
responderTask.responderOwned = true;
|
|
1828
|
+
if (parentTaskId)
|
|
1829
|
+
responderTask.parentTaskId = parentTaskId;
|
|
1830
|
+
if (isPlanTerminal(livePlan)) {
|
|
1831
|
+
livePlan.status = isPlanSuccessful(livePlan)
|
|
1832
|
+
? "completed"
|
|
1833
|
+
: "abandoned";
|
|
1834
|
+
}
|
|
1835
|
+
else if (livePlan.status !== "draft") {
|
|
1836
|
+
livePlan.status = "in_progress";
|
|
1837
|
+
}
|
|
1838
|
+
let responderPlanSaved = false;
|
|
1839
|
+
try {
|
|
1840
|
+
await savePlan(livePlan);
|
|
1841
|
+
responderPlanSaved = true;
|
|
1842
|
+
}
|
|
1843
|
+
catch {
|
|
1844
|
+
writeNotice("warn", `Responder job ${durableJob.id} started, but its plan subtask could not be persisted`, chalk.yellow(` ⚠ job ${durableJob.id} is running, but task linkage persistence failed\n`));
|
|
1845
|
+
}
|
|
1846
|
+
if (responderPlanSaved) {
|
|
1847
|
+
linkedTaskId = responderTask.id;
|
|
1848
|
+
linkedParentTaskId = parentTaskId;
|
|
1849
|
+
responderTaskId = responderTask.id;
|
|
1850
|
+
pendingSessionStatePlan = livePlan;
|
|
1851
|
+
writePlanUpdate(livePlan, renderPlanForTerminal(livePlan) + "\n");
|
|
1852
|
+
}
|
|
1853
|
+
}
|
|
1854
|
+
if (durableJob) {
|
|
1855
|
+
const linkedJob = jobManager.linkJob(durableJob.id, {
|
|
1856
|
+
...(linkedTaskId ? { taskId: linkedTaskId } : {}),
|
|
1857
|
+
...(linkedParentTaskId
|
|
1858
|
+
? { parentTaskId: linkedParentTaskId }
|
|
1859
|
+
: {}),
|
|
1860
|
+
wakeOnCompletion: true,
|
|
1861
|
+
responder: true,
|
|
1862
|
+
monitor: {
|
|
1863
|
+
...(durableJob.monitor ?? {}),
|
|
1864
|
+
toolName: call.name,
|
|
1865
|
+
toolEventId,
|
|
1866
|
+
},
|
|
1867
|
+
});
|
|
1868
|
+
if (!linkedJob) {
|
|
1869
|
+
writeNotice("warn", `Responder job ${durableJob.id} started, but durable task linkage will be retried on completion`, chalk.yellow(` ⚠ job ${durableJob.id} is running, but durable task linkage needs recovery\n`));
|
|
1870
|
+
}
|
|
1871
|
+
else if (responderTaskId) {
|
|
1872
|
+
result = {
|
|
1873
|
+
...result,
|
|
1874
|
+
output: `${result.output}\nResponder linked job ${durableJob.id} to subtask [${responderTaskId}]` +
|
|
1875
|
+
`${linkedParentTaskId ? ` under [${linkedParentTaskId}]` : ""}. ` +
|
|
1876
|
+
"This child subtask advances on its own from the real process result — do not mark, poll, or wait on it. " +
|
|
1877
|
+
"Mark your current launch step done and move to the next task now; do NOT shell.tail/shell.jobs/sleep to watch it. " +
|
|
1878
|
+
"The Responder delivers the completion into your context automatically when it is ready.",
|
|
1879
|
+
};
|
|
1880
|
+
}
|
|
1881
|
+
}
|
|
1882
|
+
}
|
|
1883
|
+
}
|
|
1742
1884
|
const output = result.output.trim();
|
|
1743
1885
|
// Always keep a full on-disk copy of tool output (any size) so the
|
|
1744
1886
|
// pager never depends on a truncated in-memory preview.
|
|
@@ -2052,7 +2194,7 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2052
2194
|
};
|
|
2053
2195
|
async function maybeAutoCompact(reason, force = false) {
|
|
2054
2196
|
const beforeTokens = estimateNextRequestTokens(messages);
|
|
2055
|
-
// E1: auto-compact trigger (default
|
|
2197
|
+
// E1: auto-compact trigger (default 72k soft; hard ceiling 100k).
|
|
2056
2198
|
const compactTrigger = autoCompactTriggerTokens();
|
|
2057
2199
|
if (!force && beforeTokens < compactTrigger)
|
|
2058
2200
|
return;
|
|
@@ -2191,6 +2333,11 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
2191
2333
|
}
|
|
2192
2334
|
else {
|
|
2193
2335
|
await maybeAutoCompact("auto-token-budget");
|
|
2336
|
+
// Safe boundary: no assistant tool-call group is open here. Refresh the
|
|
2337
|
+
// durable Responder inbox immediately before every provider request so
|
|
2338
|
+
// completions arriving mid-turn are visible without corrupting native
|
|
2339
|
+
// tool protocol or forcing a separate busy-wait loop.
|
|
2340
|
+
refreshResponderInbox();
|
|
2194
2341
|
const streamLabel = step === 0 ? "waiting for model" : `step ${step + 1}`;
|
|
2195
2342
|
let spinner = writesDirectly
|
|
2196
2343
|
? startThinkingSpinner(streamLabel, options.signal)
|
|
@@ -3034,7 +3181,8 @@ export async function runAgentTurn(prompt, options = {}) {
|
|
|
3034
3181
|
if (session.planApproved.value &&
|
|
3035
3182
|
budgetRemaining(recovery, "prematureComplete")) {
|
|
3036
3183
|
const livePlan = await loadPlan(session.sessionId).catch(() => undefined);
|
|
3037
|
-
const unfinished = livePlan?.tasks.filter((
|
|
3184
|
+
const unfinished = livePlan?.tasks.filter((task) => !task.responderOwned &&
|
|
3185
|
+
(task.state === "pending" || task.state === "in_progress"));
|
|
3038
3186
|
if (livePlan && unfinished && unfinished.length > 0) {
|
|
3039
3187
|
const next = unfinished[0];
|
|
3040
3188
|
const action = recoveryForPrematureComplete({
|