newmark-agent 0.3.8 → 0.3.10
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/context/domain/types.d.ts +1 -1
- package/dist/conversation-utility-host.bundle.cjs +1869 -721
- package/dist/conversation-utility-host.js +4 -1
- package/dist/core/agent.d.ts +23 -1
- package/dist/core/agent.js +495 -33
- package/dist/core/agentKernelRunner.js +1 -1
- package/dist/core/compressionHistoryArchive.d.ts +28 -0
- package/dist/core/compressionHistoryArchive.js +131 -0
- package/dist/core/config.js +1 -0
- package/dist/core/conversationKernel.d.ts +2 -1
- package/dist/core/conversationKernel.js +38 -7
- package/dist/core/electronBrowserUseHost.js +9 -0
- package/dist/core/electronUtilityRuntimePool.js +6 -7
- package/dist/core/runtimeLifecycle.d.ts +23 -0
- package/dist/core/runtimeLifecycle.js +146 -0
- package/dist/core/types.d.ts +3 -0
- package/dist/core/wslAgentRuntimePool.js +9 -10
- package/dist/main.js +15 -0
- package/dist/tools/index.js +6 -3
- package/dist/tools/nativeTools.js +1 -1
- package/dist/ui/index.html +0 -1
- package/dist/wsl-agent-host.bundle.cjs +1872 -723
- package/dist/wsl-agent-host.js +4 -0
- package/package.json +2 -2
package/dist/core/agent.js
CHANGED
|
@@ -66,6 +66,8 @@ const agent_context_manager_1 = require("../context/services/agent-context-manag
|
|
|
66
66
|
const toolchain_1 = require("../toolchain");
|
|
67
67
|
const agent_runtime_1 = require("../agent-runtime");
|
|
68
68
|
const performanceDiagnostics_1 = require("./performanceDiagnostics");
|
|
69
|
+
const compressionHistoryArchive_1 = require("./compressionHistoryArchive");
|
|
70
|
+
const runtimeLifecycle_1 = require("./runtimeLifecycle");
|
|
69
71
|
exports.ROOT_AGENT_ACTOR_ID = '00000000-0000-4000-8000-000000000001';
|
|
70
72
|
function normalizeIntelligenceTier(value) {
|
|
71
73
|
const tier = String(value || '').trim().toLowerCase();
|
|
@@ -193,6 +195,7 @@ class Agent {
|
|
|
193
195
|
lastCompression = null;
|
|
194
196
|
compressionCache = [];
|
|
195
197
|
nextCompressionCacheId = 1;
|
|
198
|
+
compressionHistoryArchive;
|
|
196
199
|
workspaceConversations = new Map();
|
|
197
200
|
isSubagentRuntime = false;
|
|
198
201
|
subagentName = '';
|
|
@@ -256,8 +259,10 @@ class Agent {
|
|
|
256
259
|
rootInboxListener = (message) => this.deliverRootInboxMessage(message);
|
|
257
260
|
agentOnly;
|
|
258
261
|
runtimeActorId;
|
|
262
|
+
runtimeLifecycleRole;
|
|
259
263
|
/** dev-0.3.0 context system facade (feature-flagged, default off). */
|
|
260
264
|
contextV2;
|
|
265
|
+
runtimeLifecycle;
|
|
261
266
|
/** dev-0.3.0 toolchain core (registry + capability catalog). Seeded lazily from cachedToolDefinitions; not consumed by the legacy path. */
|
|
262
267
|
toolchainCore = null;
|
|
263
268
|
/**
|
|
@@ -277,15 +282,18 @@ class Agent {
|
|
|
277
282
|
}
|
|
278
283
|
constructor(rootPath, options = {}) {
|
|
279
284
|
this.rootPath = rootPath;
|
|
285
|
+
this.runtimeLifecycle = (0, runtimeLifecycle_1.beginRuntimeLifecycle)(rootPath, options.runtimeLifecycleRole || 'main');
|
|
280
286
|
this.isSubagentRuntime = !!options.subagent;
|
|
281
287
|
this.agentOnly = !!options.agentOnly;
|
|
282
288
|
this.runtimeActorId = options.actorId || exports.ROOT_AGENT_ACTOR_ID;
|
|
289
|
+
this.runtimeLifecycleRole = options.runtimeLifecycleRole || 'main';
|
|
283
290
|
if (options.conversationId)
|
|
284
291
|
this.activeConversationId = this.safeConversationId(options.conversationId);
|
|
285
292
|
this.subagentName = options.subagentName || '';
|
|
286
293
|
this.subagentPrompt = options.subagentPrompt || '';
|
|
287
294
|
this.linkedPlanAccess = options.linkedPlanAccess;
|
|
288
295
|
this.config = new config_1.ConfigManager(rootPath);
|
|
296
|
+
this.compressionHistoryArchive = new compressionHistoryArchive_1.CompressionHistoryArchive(rootPath);
|
|
289
297
|
this.contextV2 = new agent_context_manager_1.AgentContextManager(rootPath, this.config);
|
|
290
298
|
this.agentRunService = this.config.contextFlag('agent_runtime_v2')
|
|
291
299
|
? new agent_runtime_1.AgentRunService(path.join(rootPath, '.newmark-context-v2'))
|
|
@@ -1464,6 +1472,11 @@ class Agent {
|
|
|
1464
1472
|
runId,
|
|
1465
1473
|
target,
|
|
1466
1474
|
runtimeKey: String(raw.runtimeKey || (0, conversationTarget_1.conversationRuntimeKey)(target)),
|
|
1475
|
+
runtimeOwnerId: String(raw.runtimeOwnerId || '') || undefined,
|
|
1476
|
+
runtimeOwnerPid: Number.isFinite(Number(raw.runtimeOwnerPid)) && Number(raw.runtimeOwnerPid) > 0
|
|
1477
|
+
? Math.floor(Number(raw.runtimeOwnerPid)) : undefined,
|
|
1478
|
+
runtimeLifecycleRole: ['main', 'utility', 'wsl'].includes(String(raw.runtimeLifecycleRole || ''))
|
|
1479
|
+
? String(raw.runtimeLifecycleRole) : undefined,
|
|
1467
1480
|
status,
|
|
1468
1481
|
startedAt: raw.startedAt || this.nowIso(),
|
|
1469
1482
|
endedAt: raw.endedAt,
|
|
@@ -1529,27 +1542,76 @@ class Agent {
|
|
|
1529
1542
|
const sorted = [...byRun.values()].sort((a, b) => a.startedAt.localeCompare(b.startedAt));
|
|
1530
1543
|
return sorted.length > RUN_WINDOW ? sorted.slice(-RUN_WINDOW) : sorted;
|
|
1531
1544
|
}
|
|
1532
|
-
recoverPersistedWorkRuns(runs, persistedUpdatedAt) {
|
|
1545
|
+
recoverPersistedWorkRuns(runs, persistedUpdatedAt, unexpectedExit = false) {
|
|
1533
1546
|
const normalized = this.normalizeWorkRuns(runs);
|
|
1534
1547
|
let changed = false;
|
|
1535
|
-
//
|
|
1536
|
-
//
|
|
1537
|
-
//
|
|
1538
|
-
// runtime, so preserve the running status for the UI.
|
|
1548
|
+
// A frontend tracking window can expire while the backend process keeps
|
|
1549
|
+
// running. Owner PID evidence is stronger than the persistence timestamp:
|
|
1550
|
+
// never rewrite a live backend run merely because a cold snapshot is old.
|
|
1539
1551
|
const isRecentPersist = !!persistedUpdatedAt
|
|
1540
1552
|
&& (Date.now() - new Date(persistedUpdatedAt).getTime()) < 120_000;
|
|
1541
|
-
|
|
1542
|
-
return { runs: normalized, changed: false };
|
|
1553
|
+
let preservedLiveRun = false;
|
|
1543
1554
|
for (const run of normalized) {
|
|
1544
1555
|
if (run.status !== 'running')
|
|
1545
1556
|
continue;
|
|
1557
|
+
const ownerAlive = !!run.runtimeOwnerPid && (0, runtimeLifecycle_1.isRuntimeProcessAlive)(run.runtimeOwnerPid);
|
|
1558
|
+
if (!unexpectedExit && (ownerAlive || (!run.runtimeOwnerPid && this.runtimeLifecycle.previousOwnerAlive))) {
|
|
1559
|
+
preservedLiveRun = true;
|
|
1560
|
+
continue;
|
|
1561
|
+
}
|
|
1562
|
+
if (!unexpectedExit && isRecentPersist) {
|
|
1563
|
+
preservedLiveRun = true;
|
|
1564
|
+
continue;
|
|
1565
|
+
}
|
|
1546
1566
|
run.status = 'interrupted';
|
|
1547
|
-
run.endedAt = persistedUpdatedAt || run.startedAt || this.nowIso();
|
|
1567
|
+
run.endedAt = unexpectedExit ? this.nowIso() : (persistedUpdatedAt || run.startedAt || this.nowIso());
|
|
1548
1568
|
run.expanded = true;
|
|
1569
|
+
const sequence = Math.max(0, Number(run.sequence || 0)) + 1;
|
|
1570
|
+
run.sequence = sequence;
|
|
1571
|
+
run.events.push({
|
|
1572
|
+
id: `${run.runId}-unexpected-exit-${sequence}`,
|
|
1573
|
+
conversationId: run.target.conversationId,
|
|
1574
|
+
type: 'status',
|
|
1575
|
+
content: unexpectedExit
|
|
1576
|
+
? 'Runtime exited unexpectedly; this Build was paused for recovery.'
|
|
1577
|
+
: 'Persisted running Build was recovered as interrupted.',
|
|
1578
|
+
mode: 'build',
|
|
1579
|
+
model: this.model,
|
|
1580
|
+
timestamp: run.endedAt,
|
|
1581
|
+
workspaceId: run.target.workspaceId,
|
|
1582
|
+
runtimeKey: run.runtimeKey,
|
|
1583
|
+
runId: run.runId,
|
|
1584
|
+
sequence,
|
|
1585
|
+
status: 'interrupted',
|
|
1586
|
+
});
|
|
1549
1587
|
changed = true;
|
|
1550
1588
|
}
|
|
1589
|
+
if (preservedLiveRun && !changed)
|
|
1590
|
+
return { runs: normalized, changed: false };
|
|
1551
1591
|
return { runs: normalized, changed };
|
|
1552
1592
|
}
|
|
1593
|
+
pauseFlowAfterUnexpectedExit() {
|
|
1594
|
+
if (this.mode !== 'flow' || !this.flow?.name)
|
|
1595
|
+
return false;
|
|
1596
|
+
if (this.getStoredFlowSuspension(this.activeConversationId)) {
|
|
1597
|
+
this.status = 'idle';
|
|
1598
|
+
return true;
|
|
1599
|
+
}
|
|
1600
|
+
const target = this.currentConversationTarget();
|
|
1601
|
+
this.saveStoredFlowSuspension({
|
|
1602
|
+
workflowName: this.flow.name,
|
|
1603
|
+
componentId: Math.max(0, Math.floor(Number(this.flowPc) || 0)),
|
|
1604
|
+
input: String([...this.chatMessages].reverse().find(message => message.role === 'user')?.content || ''),
|
|
1605
|
+
completedResults: [],
|
|
1606
|
+
previousMode: 'build',
|
|
1607
|
+
reason: 'interrupted',
|
|
1608
|
+
message: 'Flow paused after an unexpected runtime exit. Resume it explicitly.',
|
|
1609
|
+
target,
|
|
1610
|
+
updatedAt: this.nowIso(),
|
|
1611
|
+
}, this.activeConversationId);
|
|
1612
|
+
this.status = 'idle';
|
|
1613
|
+
return true;
|
|
1614
|
+
}
|
|
1553
1615
|
beginConversationWorkRun(runId, target = this.currentConversationTarget(), startedAt = this.nowIso(), managed = false, runtimeKey) {
|
|
1554
1616
|
const cleanRunId = String(runId || crypto.randomUUID()).trim().slice(0, 200);
|
|
1555
1617
|
const existing = this.workRuns.find(run => run.runId === cleanRunId);
|
|
@@ -1567,6 +1629,9 @@ class Agent {
|
|
|
1567
1629
|
runId: cleanRunId,
|
|
1568
1630
|
target: normalizedTarget,
|
|
1569
1631
|
runtimeKey: String(runtimeKey || (0, conversationTarget_1.conversationRuntimeKey)(normalizedTarget)),
|
|
1632
|
+
runtimeOwnerId: this.runtimeLifecycle.ownerId,
|
|
1633
|
+
runtimeOwnerPid: this.runtimeLifecycle.pid,
|
|
1634
|
+
runtimeLifecycleRole: this.runtimeLifecycleRole,
|
|
1570
1635
|
status: 'running',
|
|
1571
1636
|
startedAt,
|
|
1572
1637
|
expanded: true,
|
|
@@ -1581,10 +1646,186 @@ class Agent {
|
|
|
1581
1646
|
if (managed)
|
|
1582
1647
|
this.managedWorkRunIds.add(run.runId);
|
|
1583
1648
|
this.activeWorkRunId = run.runId;
|
|
1649
|
+
this.ensureBuildHistoryBlock(run);
|
|
1584
1650
|
if (this.agentRunService)
|
|
1585
1651
|
this.createAgentRunForWorkRun(run, normalizedTarget);
|
|
1586
1652
|
return run;
|
|
1587
1653
|
}
|
|
1654
|
+
buildHistoryStatus(status) {
|
|
1655
|
+
if (status === 'completed')
|
|
1656
|
+
return 'completed';
|
|
1657
|
+
if (status === 'error')
|
|
1658
|
+
return 'failed';
|
|
1659
|
+
if (status === 'force_interrupted')
|
|
1660
|
+
return 'cancelled';
|
|
1661
|
+
return 'paused';
|
|
1662
|
+
}
|
|
1663
|
+
ensureBuildHistoryBlock(run) {
|
|
1664
|
+
if (!this.contextV2.flags.buildHistoryPersistence)
|
|
1665
|
+
return null;
|
|
1666
|
+
try {
|
|
1667
|
+
const repository = this.contextV2.buildHistory;
|
|
1668
|
+
const existing = repository.readBlock(run.runId);
|
|
1669
|
+
if (existing)
|
|
1670
|
+
return existing;
|
|
1671
|
+
const block = {
|
|
1672
|
+
id: run.runId,
|
|
1673
|
+
conversationId: run.target.conversationId,
|
|
1674
|
+
branchId: run.branchNodeId || `conversation:${run.target.conversationId}`,
|
|
1675
|
+
parentBuildBlockId: null,
|
|
1676
|
+
startupInput: this.sanitizePublicWorkContent(run.primaryPrompt || '(user input unavailable)').slice(0, 50_000),
|
|
1677
|
+
status: 'active',
|
|
1678
|
+
createdAt: run.startedAt,
|
|
1679
|
+
startedAt: run.startedAt,
|
|
1680
|
+
completedAt: null,
|
|
1681
|
+
revision: 1,
|
|
1682
|
+
activeCheckpointId: null,
|
|
1683
|
+
tokenBudgetPolicyId: 'default',
|
|
1684
|
+
metadata: {
|
|
1685
|
+
runtimeKey: run.runtimeKey,
|
|
1686
|
+
conversationWorkRunId: run.runId,
|
|
1687
|
+
},
|
|
1688
|
+
};
|
|
1689
|
+
repository.saveBlock(block);
|
|
1690
|
+
return block;
|
|
1691
|
+
}
|
|
1692
|
+
catch (error) {
|
|
1693
|
+
console.error(`[NewmarkBuildHistory] unable to create Build Block ${run.runId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1694
|
+
return null;
|
|
1695
|
+
}
|
|
1696
|
+
}
|
|
1697
|
+
finalWorkRunSummary(run) {
|
|
1698
|
+
const finalEvent = [...run.events].reverse().find(event => event.type === 'final_response' || event.type === 'response');
|
|
1699
|
+
if (finalEvent?.content)
|
|
1700
|
+
return this.sanitizePublicWorkContent(finalEvent.content).slice(0, 4_000);
|
|
1701
|
+
const finalMessage = [...this.chatMessages].reverse().find(message => message.role === 'assistant' && message.runId === run.runId);
|
|
1702
|
+
return this.sanitizePublicWorkContent(finalMessage?.content || '(no final summary)').slice(0, 4_000);
|
|
1703
|
+
}
|
|
1704
|
+
auditGoalAtWorkRunEnd(run, status, endedAt) {
|
|
1705
|
+
if (!this.goal)
|
|
1706
|
+
return { tracked: false, checked: false, matched: false, completed: false, checkedAt: endedAt };
|
|
1707
|
+
const objective = this.goal.objective;
|
|
1708
|
+
const pausedBeforeSync = this.goal.paused;
|
|
1709
|
+
const matched = this.goal.checkComplete(this.finalWorkRunSummary(run));
|
|
1710
|
+
const completed = status === 'completed' && matched && !pausedBeforeSync;
|
|
1711
|
+
if (completed)
|
|
1712
|
+
this.markGoalComplete();
|
|
1713
|
+
return {
|
|
1714
|
+
tracked: true,
|
|
1715
|
+
checked: true,
|
|
1716
|
+
matched,
|
|
1717
|
+
completed,
|
|
1718
|
+
objective,
|
|
1719
|
+
paused: completed ? false : pausedBeforeSync,
|
|
1720
|
+
checkedAt: endedAt,
|
|
1721
|
+
};
|
|
1722
|
+
}
|
|
1723
|
+
enforceGoalTerminalInvariant(status, goalAudit) {
|
|
1724
|
+
if (!this.goal)
|
|
1725
|
+
return;
|
|
1726
|
+
const continuationIsOwned = status === 'completed' && !goalAudit.completed && !!this.goalContinuationGate;
|
|
1727
|
+
if (continuationIsOwned)
|
|
1728
|
+
return;
|
|
1729
|
+
if (!this.goal.paused) {
|
|
1730
|
+
this.goal.paused = true;
|
|
1731
|
+
this.status = 'goal_paused';
|
|
1732
|
+
this.saveWorkspaceConversationState(true);
|
|
1733
|
+
}
|
|
1734
|
+
goalAudit.paused = this.goal.paused;
|
|
1735
|
+
}
|
|
1736
|
+
persistBuildBlockWorkOverview(run, status, endedAt, goalAudit) {
|
|
1737
|
+
if (!this.contextV2.flags.buildHistoryPersistence)
|
|
1738
|
+
return;
|
|
1739
|
+
try {
|
|
1740
|
+
const repository = this.contextV2.buildHistory;
|
|
1741
|
+
let block = this.ensureBuildHistoryBlock(run);
|
|
1742
|
+
if (!block)
|
|
1743
|
+
return;
|
|
1744
|
+
const nextStatus = this.buildHistoryStatus(status);
|
|
1745
|
+
const nextCompletedAt = nextStatus === 'active' ? null : endedAt;
|
|
1746
|
+
const startupInput = this.sanitizePublicWorkContent(run.primaryPrompt || block.startupInput || '(user input unavailable)').slice(0, 50_000);
|
|
1747
|
+
if (block.status !== nextStatus || block.completedAt !== nextCompletedAt || block.startupInput !== startupInput) {
|
|
1748
|
+
const transitioned = repository.transitionBlock(block, {
|
|
1749
|
+
startupInput,
|
|
1750
|
+
status: nextStatus,
|
|
1751
|
+
completedAt: nextCompletedAt,
|
|
1752
|
+
metadata: {
|
|
1753
|
+
...block.metadata,
|
|
1754
|
+
terminalWorkRunStatus: status,
|
|
1755
|
+
terminalAt: endedAt,
|
|
1756
|
+
},
|
|
1757
|
+
}, {
|
|
1758
|
+
expectedRevision: block.revision,
|
|
1759
|
+
operationId: `work-run-terminal:${run.runId}:${status}:${endedAt}`,
|
|
1760
|
+
});
|
|
1761
|
+
block = transitioned.applied ? transitioned.block : (repository.readBlock(run.runId) || block);
|
|
1762
|
+
}
|
|
1763
|
+
const eventCounts = run.events.reduce((counts, event) => {
|
|
1764
|
+
counts[event.type] = (counts[event.type] || 0) + 1;
|
|
1765
|
+
return counts;
|
|
1766
|
+
}, {});
|
|
1767
|
+
const guides = run.guides.slice(-8).map(guide => `${guide.status}: ${this.sanitizePublicWorkContent(String(guide.content || '')).slice(0, 500)}`);
|
|
1768
|
+
const finalSummary = this.finalWorkRunSummary(run);
|
|
1769
|
+
const goalLine = goalAudit.tracked
|
|
1770
|
+
? `Goal: checked=${goalAudit.checked}; matched=${goalAudit.matched}; completed=${goalAudit.completed}; paused=${goalAudit.paused}; objective=${goalAudit.objective}`
|
|
1771
|
+
: 'Goal: not tracked for this Build Block.';
|
|
1772
|
+
const content = [
|
|
1773
|
+
'Build Block Work Overview',
|
|
1774
|
+
`Run: ${run.runId}`,
|
|
1775
|
+
`Status: ${status}`,
|
|
1776
|
+
`Conversation: ${run.target.conversationId}`,
|
|
1777
|
+
`Startup input: ${this.sanitizePublicWorkContent(run.primaryPrompt || block.startupInput || '(user input unavailable)').slice(0, 2_000)}`,
|
|
1778
|
+
`Final summary: ${finalSummary}`,
|
|
1779
|
+
`Events: ${JSON.stringify(eventCounts)}`,
|
|
1780
|
+
guides.length ? `Guides: ${guides.join(' | ')}` : 'Guides: none',
|
|
1781
|
+
goalLine,
|
|
1782
|
+
].join('\n');
|
|
1783
|
+
repository.appendEntry({
|
|
1784
|
+
buildBlockId: run.runId,
|
|
1785
|
+
type: 'work_overview',
|
|
1786
|
+
content: content.slice(0, 50_000),
|
|
1787
|
+
source: 'agent',
|
|
1788
|
+
importance: status === 'completed' ? 'normal' : 'high',
|
|
1789
|
+
structuredData: {
|
|
1790
|
+
conversationId: run.target.conversationId,
|
|
1791
|
+
branchId: block.branchId,
|
|
1792
|
+
status,
|
|
1793
|
+
endedAt,
|
|
1794
|
+
finalSummary,
|
|
1795
|
+
eventCounts,
|
|
1796
|
+
guideCount: run.guides.length,
|
|
1797
|
+
goal: goalAudit,
|
|
1798
|
+
},
|
|
1799
|
+
operationId: `work-run-overview:${run.runId}`,
|
|
1800
|
+
revision: block.revision,
|
|
1801
|
+
});
|
|
1802
|
+
}
|
|
1803
|
+
catch (error) {
|
|
1804
|
+
console.error(`[NewmarkBuildHistory] unable to write Build Block overview ${run.runId}: ${error instanceof Error ? error.message : String(error)}`);
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
recoverMissingTerminalBuildOverviews() {
|
|
1808
|
+
if (!this.contextV2.flags.buildHistoryPersistence)
|
|
1809
|
+
return;
|
|
1810
|
+
const repository = this.contextV2.buildHistory;
|
|
1811
|
+
for (const run of this.workRuns) {
|
|
1812
|
+
if (run.status === 'running')
|
|
1813
|
+
continue;
|
|
1814
|
+
try {
|
|
1815
|
+
if (repository.readEntries(run.runId).some(entry => entry.type === 'work_overview'))
|
|
1816
|
+
continue;
|
|
1817
|
+
}
|
|
1818
|
+
catch {
|
|
1819
|
+
continue;
|
|
1820
|
+
}
|
|
1821
|
+
const endedAt = run.endedAt || this.nowIso();
|
|
1822
|
+
if (run.status === 'completed')
|
|
1823
|
+
this.ensureCompletedWorkRunFinalResult(run);
|
|
1824
|
+
const goalAudit = this.auditGoalAtWorkRunEnd(run, run.status, endedAt);
|
|
1825
|
+
this.enforceGoalTerminalInvariant(run.status, goalAudit);
|
|
1826
|
+
this.persistBuildBlockWorkOverview(run, run.status, endedAt, goalAudit);
|
|
1827
|
+
}
|
|
1828
|
+
}
|
|
1588
1829
|
createAgentRunForWorkRun(run, target) {
|
|
1589
1830
|
if (!this.agentRunService)
|
|
1590
1831
|
return;
|
|
@@ -1827,10 +2068,22 @@ class Agent {
|
|
|
1827
2068
|
return false;
|
|
1828
2069
|
this.syncAgentRunTerminal(run.runId, status, endedAt);
|
|
1829
2070
|
if (run.status !== 'running') {
|
|
1830
|
-
if (run.status !== 'interrupted' || status !== 'force_interrupted')
|
|
1831
|
-
|
|
2071
|
+
if (run.status !== 'interrupted' || status !== 'force_interrupted') {
|
|
2072
|
+
if (run.status !== status)
|
|
2073
|
+
return false;
|
|
2074
|
+
const terminalAt = run.endedAt || endedAt;
|
|
2075
|
+
if (status === 'completed')
|
|
2076
|
+
this.ensureCompletedWorkRunFinalResult(run);
|
|
2077
|
+
const goalAudit = this.auditGoalAtWorkRunEnd(run, status, terminalAt);
|
|
2078
|
+
this.enforceGoalTerminalInvariant(status, goalAudit);
|
|
2079
|
+
this.persistBuildBlockWorkOverview(run, status, terminalAt, goalAudit);
|
|
2080
|
+
this.saveWorkspaceConversationState();
|
|
2081
|
+
return true;
|
|
2082
|
+
}
|
|
1832
2083
|
this.activeWorkRunId = run.runId;
|
|
1833
2084
|
this.finalizingWorkRunId = run.runId;
|
|
2085
|
+
const goalAudit = this.auditGoalAtWorkRunEnd(run, status, endedAt);
|
|
2086
|
+
this.enforceGoalTerminalInvariant(status, goalAudit);
|
|
1834
2087
|
this.emitWorkEvent({
|
|
1835
2088
|
type: 'status',
|
|
1836
2089
|
content: 'Force interrupted.',
|
|
@@ -1845,6 +2098,7 @@ class Agent {
|
|
|
1845
2098
|
this.activeWorkRunId = '';
|
|
1846
2099
|
this.finalizingWorkRunId = '';
|
|
1847
2100
|
this.managedWorkRunIds.delete(run.runId);
|
|
2101
|
+
this.persistBuildBlockWorkOverview(run, status, endedAt, goalAudit);
|
|
1848
2102
|
this.saveWorkspaceConversationState();
|
|
1849
2103
|
return true;
|
|
1850
2104
|
}
|
|
@@ -1852,6 +2106,8 @@ class Agent {
|
|
|
1852
2106
|
this.finalizingWorkRunId = run.runId;
|
|
1853
2107
|
if (status === 'completed')
|
|
1854
2108
|
this.ensureCompletedWorkRunFinalResult(run);
|
|
2109
|
+
const goalAudit = this.auditGoalAtWorkRunEnd(run, status, endedAt);
|
|
2110
|
+
this.enforceGoalTerminalInvariant(status, goalAudit);
|
|
1855
2111
|
this.emitWorkEvent({
|
|
1856
2112
|
type: status === 'completed' ? 'done' : status === 'error' ? 'error' : 'status',
|
|
1857
2113
|
content: status === 'force_interrupted' ? 'Force interrupted.' : status === 'interrupted' ? 'Interrupted.' : 'Response complete.',
|
|
@@ -1866,6 +2122,7 @@ class Agent {
|
|
|
1866
2122
|
this.activeWorkRunId = '';
|
|
1867
2123
|
this.finalizingWorkRunId = '';
|
|
1868
2124
|
this.managedWorkRunIds.delete(run.runId);
|
|
2125
|
+
this.persistBuildBlockWorkOverview(run, status, endedAt, goalAudit);
|
|
1869
2126
|
this.saveWorkspaceConversationState();
|
|
1870
2127
|
return true;
|
|
1871
2128
|
}
|
|
@@ -1972,6 +2229,14 @@ class Agent {
|
|
|
1972
2229
|
this.saveWorkspaceConversationState(false);
|
|
1973
2230
|
if (event.type === 'done' || event.type === 'error')
|
|
1974
2231
|
this.saveWorkspaceConversationState(true);
|
|
2232
|
+
if (activeRun && this.finalizingWorkRunId !== activeRun.runId
|
|
2233
|
+
&& (event.type === 'done' || event.type === 'error') && activeRun.status !== 'running') {
|
|
2234
|
+
const terminalStatus = activeRun.status;
|
|
2235
|
+
const terminalAt = activeRun.endedAt || this.nowIso();
|
|
2236
|
+
const goalAudit = this.auditGoalAtWorkRunEnd(activeRun, terminalStatus, terminalAt);
|
|
2237
|
+
this.enforceGoalTerminalInvariant(terminalStatus, goalAudit);
|
|
2238
|
+
this.persistBuildBlockWorkOverview(activeRun, terminalStatus, terminalAt, goalAudit);
|
|
2239
|
+
}
|
|
1975
2240
|
return event;
|
|
1976
2241
|
}
|
|
1977
2242
|
appendWorkflowMessage(content, toolName, toolArgs, persist = true) {
|
|
@@ -3101,6 +3366,17 @@ class Agent {
|
|
|
3101
3366
|
}
|
|
3102
3367
|
return cleaned;
|
|
3103
3368
|
}
|
|
3369
|
+
activeConversationRuntimeOwner() {
|
|
3370
|
+
const activeGoal = !!this.goal && !this.goal.paused;
|
|
3371
|
+
const activeFlow = this.mode === 'flow' && !!this.flow?.name && !this.getStoredFlowSuspension(this.activeConversationId);
|
|
3372
|
+
if (!activeGoal && !activeFlow)
|
|
3373
|
+
return {};
|
|
3374
|
+
return {
|
|
3375
|
+
runtimeOwnerId: this.runtimeLifecycle.ownerId,
|
|
3376
|
+
runtimeOwnerPid: this.runtimeLifecycle.pid,
|
|
3377
|
+
runtimeLifecycleRole: this.runtimeLifecycleRole,
|
|
3378
|
+
};
|
|
3379
|
+
}
|
|
3104
3380
|
saveWorkspaceConversationState(flush = true) {
|
|
3105
3381
|
if (this.isSubagentRuntime)
|
|
3106
3382
|
return;
|
|
@@ -3108,6 +3384,7 @@ class Agent {
|
|
|
3108
3384
|
if (!key)
|
|
3109
3385
|
return;
|
|
3110
3386
|
const updatedAt = new Date().toISOString();
|
|
3387
|
+
const runtimeOwner = this.activeConversationRuntimeOwner();
|
|
3111
3388
|
this.workspaceConversations.set(key, {
|
|
3112
3389
|
chatMessages: [...this.chatMessages],
|
|
3113
3390
|
history: [...this.history],
|
|
@@ -3122,6 +3399,9 @@ class Agent {
|
|
|
3122
3399
|
inputMode: this.inputMode,
|
|
3123
3400
|
mode: this.mode,
|
|
3124
3401
|
goal: this.serializeGoal(),
|
|
3402
|
+
runtimeOwnerId: runtimeOwner.runtimeOwnerId,
|
|
3403
|
+
runtimeOwnerPid: runtimeOwner.runtimeOwnerPid,
|
|
3404
|
+
runtimeLifecycleRole: runtimeOwner.runtimeLifecycleRole,
|
|
3125
3405
|
updatedAt,
|
|
3126
3406
|
});
|
|
3127
3407
|
const stored = this.readStoredConversationState();
|
|
@@ -3152,6 +3432,9 @@ class Agent {
|
|
|
3152
3432
|
inputMode: this.inputMode,
|
|
3153
3433
|
mode: this.mode,
|
|
3154
3434
|
goal: this.serializeGoal(),
|
|
3435
|
+
runtimeOwnerId: runtimeOwner.runtimeOwnerId,
|
|
3436
|
+
runtimeOwnerPid: runtimeOwner.runtimeOwnerPid,
|
|
3437
|
+
runtimeLifecycleRole: runtimeOwner.runtimeLifecycleRole,
|
|
3155
3438
|
updatedAt,
|
|
3156
3439
|
};
|
|
3157
3440
|
if (nextEntry.tree || nextEntry.branches?.length) {
|
|
@@ -3203,6 +3486,7 @@ class Agent {
|
|
|
3203
3486
|
this.mode = saved.mode || 'build';
|
|
3204
3487
|
this.goal = this.restoreGoal(saved.goal);
|
|
3205
3488
|
this.status = this.restoreStatusFromWorkRuns(saved.goal);
|
|
3489
|
+
this.recoverMissingTerminalBuildOverviews();
|
|
3206
3490
|
this.activeWorkRunId = this.workRuns.find(run => run.status === 'running')?.runId || '';
|
|
3207
3491
|
return;
|
|
3208
3492
|
}
|
|
@@ -3215,7 +3499,7 @@ class Agent {
|
|
|
3215
3499
|
this.chatMessages = this.normalizeConversationChatMessages(persisted?.chatMessages || [], this.history);
|
|
3216
3500
|
this.conversationPlan = this.normalizeConversationPlan(persisted?.plan);
|
|
3217
3501
|
this.linkedPlan = this.normalizeLinkedPlan(persisted?.linkedPlan);
|
|
3218
|
-
const recoveredWorkRuns = this.recoverPersistedWorkRuns(persisted?.workRuns, persisted?.updatedAt);
|
|
3502
|
+
const recoveredWorkRuns = this.recoverPersistedWorkRuns(persisted?.workRuns, persisted?.updatedAt, this.runtimeLifecycle.unexpectedExit);
|
|
3219
3503
|
this.workRuns = recoveredWorkRuns.runs;
|
|
3220
3504
|
this.continuations = this.normalizeContinuations(persisted?.continuations);
|
|
3221
3505
|
this.restoreConversationModelSelection(persisted?.modelSelection);
|
|
@@ -3223,9 +3507,29 @@ class Agent {
|
|
|
3223
3507
|
this.inputMode = this.defaultInputMode();
|
|
3224
3508
|
this.mode = persisted?.mode || 'build';
|
|
3225
3509
|
this.goal = this.restoreGoal(persisted?.goal);
|
|
3226
|
-
|
|
3510
|
+
const persistedRuntimeOwnerPid = Math.floor(Number(persisted?.runtimeOwnerPid) || 0);
|
|
3511
|
+
const persistedRuntimeOwnerKnown = persistedRuntimeOwnerPid > 0;
|
|
3512
|
+
const persistedRuntimeOwnerAlive = persistedRuntimeOwnerKnown && (0, runtimeLifecycle_1.isRuntimeProcessAlive)(persistedRuntimeOwnerPid);
|
|
3513
|
+
const runtimeOwnerLost = persistedRuntimeOwnerKnown ? !persistedRuntimeOwnerAlive : this.runtimeLifecycle.unexpectedExit;
|
|
3514
|
+
const shouldPauseRecoveredState = recoveredWorkRuns.changed || runtimeOwnerLost;
|
|
3515
|
+
let goalPausedByRecovery = false;
|
|
3516
|
+
if (shouldPauseRecoveredState && this.goal && !this.goal.paused) {
|
|
3517
|
+
this.goal.paused = true;
|
|
3518
|
+
goalPausedByRecovery = true;
|
|
3519
|
+
}
|
|
3520
|
+
this.status = this.restoreStatusFromWorkRuns(this.serializeGoal());
|
|
3521
|
+
const flowPausedByRecovery = shouldPauseRecoveredState && this.pauseFlowAfterUnexpectedExit();
|
|
3227
3522
|
this.activeWorkRunId = this.workRuns.find(run => run.status === 'running')?.runId || '';
|
|
3228
3523
|
this.bindConversationSubagents(this.activeConversationId, persisted?.subagentState);
|
|
3524
|
+
if (recoveredWorkRuns.changed) {
|
|
3525
|
+
for (const run of recoveredWorkRuns.runs.filter(item => item.status === 'interrupted')) {
|
|
3526
|
+
const audit = this.auditGoalAtWorkRunEnd(run, 'interrupted', run.endedAt || this.nowIso());
|
|
3527
|
+
this.persistBuildBlockWorkOverview(run, 'interrupted', run.endedAt || this.nowIso(), audit);
|
|
3528
|
+
}
|
|
3529
|
+
}
|
|
3530
|
+
this.recoverMissingTerminalBuildOverviews();
|
|
3531
|
+
const recoveryApplied = recoveredWorkRuns.changed || runtimeOwnerLost || goalPausedByRecovery || flowPausedByRecovery;
|
|
3532
|
+
const recoveryAt = recoveryApplied ? this.nowIso() : persisted?.updatedAt;
|
|
3229
3533
|
this.workspaceConversations.set(key, {
|
|
3230
3534
|
chatMessages: [...this.chatMessages],
|
|
3231
3535
|
history: [...this.history],
|
|
@@ -3236,21 +3540,31 @@ class Agent {
|
|
|
3236
3540
|
workRuns: this.normalizeWorkRuns(this.workRuns),
|
|
3237
3541
|
continuations: this.normalizeContinuations(this.continuations),
|
|
3238
3542
|
modelSelection: persisted?.modelSelection || this.currentConversationModelSelection(),
|
|
3239
|
-
flowSelection:
|
|
3543
|
+
flowSelection: this.currentConversationFlowSelection(),
|
|
3240
3544
|
inputMode: this.defaultInputMode(),
|
|
3241
|
-
mode:
|
|
3242
|
-
goal:
|
|
3243
|
-
|
|
3545
|
+
mode: this.mode,
|
|
3546
|
+
goal: this.serializeGoal(),
|
|
3547
|
+
runtimeOwnerId: this.activeConversationRuntimeOwner().runtimeOwnerId,
|
|
3548
|
+
runtimeOwnerPid: this.activeConversationRuntimeOwner().runtimeOwnerPid,
|
|
3549
|
+
runtimeLifecycleRole: this.activeConversationRuntimeOwner().runtimeLifecycleRole,
|
|
3550
|
+
updatedAt: recoveryAt,
|
|
3244
3551
|
});
|
|
3245
|
-
if (
|
|
3246
|
-
const
|
|
3247
|
-
|
|
3248
|
-
|
|
3552
|
+
if (recoveryApplied && stateKey && persisted) {
|
|
3553
|
+
const recoveredStored = this.readStoredConversationState();
|
|
3554
|
+
recoveredStored.conversations = recoveredStored.conversations || {};
|
|
3555
|
+
recoveredStored.conversations[stateKey] = {
|
|
3556
|
+
...(recoveredStored.conversations[stateKey] || persisted),
|
|
3249
3557
|
workRuns: this.normalizeWorkRuns(this.workRuns),
|
|
3250
|
-
|
|
3558
|
+
flowSelection: this.currentConversationFlowSelection(),
|
|
3559
|
+
mode: this.mode,
|
|
3560
|
+
goal: this.serializeGoal(),
|
|
3561
|
+
runtimeOwnerId: this.activeConversationRuntimeOwner().runtimeOwnerId,
|
|
3562
|
+
runtimeOwnerPid: this.activeConversationRuntimeOwner().runtimeOwnerPid,
|
|
3563
|
+
runtimeLifecycleRole: this.activeConversationRuntimeOwner().runtimeLifecycleRole,
|
|
3564
|
+
updatedAt: recoveryAt,
|
|
3251
3565
|
};
|
|
3252
|
-
this.workspaceConversations.get(key).updatedAt =
|
|
3253
|
-
this.writeStoredConversationStateNow(
|
|
3566
|
+
this.workspaceConversations.get(key).updatedAt = recoveryAt;
|
|
3567
|
+
this.writeStoredConversationStateNow(recoveredStored);
|
|
3254
3568
|
}
|
|
3255
3569
|
}
|
|
3256
3570
|
applyWorkspaceContext(ws) {
|
|
@@ -3477,7 +3791,7 @@ class Agent {
|
|
|
3477
3791
|
catch { }
|
|
3478
3792
|
const action = String(input.action || '').trim();
|
|
3479
3793
|
if (!action)
|
|
3480
|
-
return { ok: false, output: '[context_history_manage] action is required (list|remove|summarize|restore|search|status).', error: 'action is required.' };
|
|
3794
|
+
return { ok: false, output: '[context_history_manage] action is required (list|remove|summarize|restore|search|read|status).', error: 'action is required.' };
|
|
3481
3795
|
if (action === 'list') {
|
|
3482
3796
|
const limit = Math.max(5, Math.min(400, Math.floor(Number(input.limit || 200))));
|
|
3483
3797
|
const entries = this.history.slice(0, limit).map((message, index) => ({
|
|
@@ -3569,7 +3883,9 @@ class Agent {
|
|
|
3569
3883
|
}
|
|
3570
3884
|
if (action === 'restore') {
|
|
3571
3885
|
const restoreId = String(input.restore_id || '').trim();
|
|
3572
|
-
const
|
|
3886
|
+
const hotEntry = this.compressionCache.find(item => item.id === restoreId);
|
|
3887
|
+
const coldEntry = hotEntry ? undefined : this.coldCompressionEntries().find(item => item.id === restoreId);
|
|
3888
|
+
const entry = hotEntry || coldEntry;
|
|
3573
3889
|
if (!entry)
|
|
3574
3890
|
return { ok: false, output: `[context_history_manage] restore unknown restore_id: ${restoreId}.`, error: 'restore_id not found.' };
|
|
3575
3891
|
const summaryHeader = entry.summary.startsWith('[Context Compression') ? '[Context Compression' : '[Context History Summary]';
|
|
@@ -3578,7 +3894,10 @@ class Agent {
|
|
|
3578
3894
|
return { ok: false, output: '[context_history_manage] restore failed: the folded summary is no longer present in context history (already re-folded or removed).', error: 'restore target summary not found in history.' };
|
|
3579
3895
|
}
|
|
3580
3896
|
this.history.splice(markerIndex, 1, ...entry.messages.map(message => ({ ...message })));
|
|
3581
|
-
|
|
3897
|
+
if (hotEntry)
|
|
3898
|
+
this.compressionCache = this.compressionCache.filter(item => item.id !== entry.id);
|
|
3899
|
+
if (coldEntry)
|
|
3900
|
+
this.markColdCompressionRestored(entry.id);
|
|
3582
3901
|
this.saveWorkspaceConversationState(true);
|
|
3583
3902
|
return {
|
|
3584
3903
|
ok: true,
|
|
@@ -3588,7 +3907,9 @@ class Agent {
|
|
|
3588
3907
|
restoreId: entry.id,
|
|
3589
3908
|
restoredEntries: entry.messages.length,
|
|
3590
3909
|
restoredChars: entry.foldedChars,
|
|
3910
|
+
source: hotEntry ? 'hot-cache' : 'cold-archive',
|
|
3591
3911
|
cacheRemaining: this.compressionCache.length,
|
|
3912
|
+
archiveRemaining: this.coldCompressionEntries().length,
|
|
3592
3913
|
displayHistory: { untouched: true, messageCount: this.chatMessages.length },
|
|
3593
3914
|
}, null, 2),
|
|
3594
3915
|
metadata: { kind: 'context-history-restore' },
|
|
@@ -3600,10 +3921,16 @@ class Agent {
|
|
|
3600
3921
|
if (!query)
|
|
3601
3922
|
return { ok: false, output: '[context_history_manage] search requires query.', error: 'search requires query.' };
|
|
3602
3923
|
const matches = [];
|
|
3603
|
-
|
|
3924
|
+
const coldEntries = this.coldCompressionEntries();
|
|
3925
|
+
const searchable = [
|
|
3926
|
+
...this.compressionCache.map(entry => ({ entry, source: 'hot-cache' })),
|
|
3927
|
+
...coldEntries.map(entry => ({ entry, source: 'cold-archive' })),
|
|
3928
|
+
];
|
|
3929
|
+
for (const item of searchable) {
|
|
3604
3930
|
if (matches.length >= limit)
|
|
3605
3931
|
break;
|
|
3606
|
-
const
|
|
3932
|
+
const { entry } = item;
|
|
3933
|
+
const hit = { cacheId: entry.id, source: item.source, at: entry.at, summary: entry.summary.slice(0, 500), matches: [] };
|
|
3607
3934
|
if (entry.summary.toLowerCase().includes(query)) {
|
|
3608
3935
|
hit.matches.push({ index: -1, snippet: this.snippetAround(entry.summary, query) });
|
|
3609
3936
|
}
|
|
@@ -3625,18 +3952,78 @@ class Agent {
|
|
|
3625
3952
|
action: 'search',
|
|
3626
3953
|
query: String(input.query || ''),
|
|
3627
3954
|
cacheEntries: this.compressionCache.length,
|
|
3955
|
+
archiveEntries: coldEntries.length,
|
|
3628
3956
|
matches,
|
|
3629
3957
|
displayHistory: { untouched: true, messageCount: this.chatMessages.length },
|
|
3630
3958
|
}, null, 2),
|
|
3631
3959
|
metadata: { kind: 'context-history-search' },
|
|
3632
3960
|
};
|
|
3633
3961
|
}
|
|
3962
|
+
if (action === 'read') {
|
|
3963
|
+
const restoreId = String(input.restore_id || '').trim();
|
|
3964
|
+
if (!restoreId)
|
|
3965
|
+
return { ok: false, output: '[context_history_manage] read requires restore_id.', error: 'read requires restore_id.' };
|
|
3966
|
+
const hotEntry = this.compressionCache.find(item => item.id === restoreId);
|
|
3967
|
+
const coldEntry = hotEntry ? undefined : this.coldCompressionEntries().find(item => item.id === restoreId);
|
|
3968
|
+
const entry = hotEntry || coldEntry;
|
|
3969
|
+
if (!entry)
|
|
3970
|
+
return { ok: false, output: `[context_history_manage] read unknown restore_id: ${restoreId}.`, error: 'restore_id not found.' };
|
|
3971
|
+
const offset = Math.max(0, Math.floor(Number(input.offset || 0)));
|
|
3972
|
+
const contentOffset = Math.max(0, Math.floor(Number(input.content_offset || 0)));
|
|
3973
|
+
const limit = Math.max(1, Math.min(100, Math.floor(Number(input.limit || 20))));
|
|
3974
|
+
const maxChars = Math.max(1_000, Math.min(60_000, Math.floor(Number(input.max_chars || 12_000))));
|
|
3975
|
+
const messages = [];
|
|
3976
|
+
let emittedChars = 0;
|
|
3977
|
+
let nextOffset = null;
|
|
3978
|
+
let nextContentOffset = 0;
|
|
3979
|
+
for (let index = offset; index < entry.messages.length && messages.length < limit; index += 1) {
|
|
3980
|
+
const message = entry.messages[index];
|
|
3981
|
+
const remaining = maxChars - emittedChars;
|
|
3982
|
+
if (remaining <= 0)
|
|
3983
|
+
break;
|
|
3984
|
+
const fullContent = this.compressionHistoryContent(message.content || message.reasoning_content || '');
|
|
3985
|
+
const start = index === offset ? Math.min(contentOffset, fullContent.length) : 0;
|
|
3986
|
+
const content = fullContent.slice(start, start + remaining);
|
|
3987
|
+
const truncated = start + content.length < fullContent.length;
|
|
3988
|
+
messages.push({ index, role: String(message.role || ''), name: String(message.name || ''), contentOffset: start, content, truncated });
|
|
3989
|
+
emittedChars += content.length;
|
|
3990
|
+
if (truncated) {
|
|
3991
|
+
nextOffset = index;
|
|
3992
|
+
nextContentOffset = start + content.length;
|
|
3993
|
+
break;
|
|
3994
|
+
}
|
|
3995
|
+
}
|
|
3996
|
+
if (nextOffset === null && offset + messages.length < entry.messages.length)
|
|
3997
|
+
nextOffset = offset + messages.length;
|
|
3998
|
+
return {
|
|
3999
|
+
ok: true,
|
|
4000
|
+
output: JSON.stringify({
|
|
4001
|
+
ok: true,
|
|
4002
|
+
action: 'read',
|
|
4003
|
+
restoreId: entry.id,
|
|
4004
|
+
source: hotEntry ? 'hot-cache' : 'cold-archive',
|
|
4005
|
+
at: entry.at,
|
|
4006
|
+
foldedEntries: entry.foldedEntries,
|
|
4007
|
+
foldedChars: entry.foldedChars,
|
|
4008
|
+
offset,
|
|
4009
|
+
contentOffset,
|
|
4010
|
+
returned: messages.length,
|
|
4011
|
+
nextOffset,
|
|
4012
|
+
nextContentOffset: nextOffset === null ? null : nextContentOffset,
|
|
4013
|
+
summary: entry.summary.slice(0, 2_000),
|
|
4014
|
+
messages,
|
|
4015
|
+
displayHistory: { untouched: true, messageCount: this.chatMessages.length },
|
|
4016
|
+
}, null, 2),
|
|
4017
|
+
metadata: { kind: 'context-history-read' },
|
|
4018
|
+
};
|
|
4019
|
+
}
|
|
3634
4020
|
if (action === 'status') {
|
|
3635
4021
|
const budget = this.compressionBudget(this.history);
|
|
3636
4022
|
const estimatedTokens = this.estimateContextTokens(this.history);
|
|
3637
4023
|
const maxTokens = this.contextMaxTokens();
|
|
3638
4024
|
const protectedStartIndex = this.contextHistoryProtectedStartIndex();
|
|
3639
4025
|
const lastUserIndex = this.history.map(message => String(message.role || '')).lastIndexOf('user');
|
|
4026
|
+
const coldEntries = this.coldCompressionEntries();
|
|
3640
4027
|
return {
|
|
3641
4028
|
ok: true,
|
|
3642
4029
|
output: JSON.stringify({
|
|
@@ -3666,6 +4053,13 @@ class Agent {
|
|
|
3666
4053
|
totalFoldedChars: this.compressionCache.reduce((sum, item) => sum + item.foldedChars, 0),
|
|
3667
4054
|
ids: this.compressionCache.map(item => item.id),
|
|
3668
4055
|
},
|
|
4056
|
+
archive: {
|
|
4057
|
+
enabled: this.config.getBool('context', 'compression_archive_enabled'),
|
|
4058
|
+
entries: coldEntries.length,
|
|
4059
|
+
totalFoldedEntries: coldEntries.reduce((sum, item) => sum + item.foldedEntries, 0),
|
|
4060
|
+
totalFoldedChars: coldEntries.reduce((sum, item) => sum + item.foldedChars, 0),
|
|
4061
|
+
recentIds: coldEntries.slice(-50).map(item => item.id),
|
|
4062
|
+
},
|
|
3669
4063
|
protectedZone: {
|
|
3670
4064
|
preserveRecentMessages: this.config.getNum('context', 'preserve_recent_messages') || 5,
|
|
3671
4065
|
protectedStartIndex,
|
|
@@ -4186,6 +4580,7 @@ class Agent {
|
|
|
4186
4580
|
};
|
|
4187
4581
|
}
|
|
4188
4582
|
updateGoal(newGoal) {
|
|
4583
|
+
const existingGoalPaused = this.goal?.paused || false;
|
|
4189
4584
|
if (this.goal) {
|
|
4190
4585
|
this.goal.update(newGoal);
|
|
4191
4586
|
}
|
|
@@ -4195,9 +4590,10 @@ class Agent {
|
|
|
4195
4590
|
}
|
|
4196
4591
|
if (this.goal) {
|
|
4197
4592
|
this.goal.verified = false;
|
|
4198
|
-
|
|
4593
|
+
// Replacing the objective must not implicitly resume an explicitly paused Goal.
|
|
4594
|
+
this.goal.paused = existingGoalPaused;
|
|
4199
4595
|
this.goal.goalRounds = 0;
|
|
4200
|
-
this.status = 'idle';
|
|
4596
|
+
this.status = this.goal.paused ? 'goal_paused' : 'idle';
|
|
4201
4597
|
}
|
|
4202
4598
|
this.mode = 'goal';
|
|
4203
4599
|
this.saveWorkspaceConversationState(true);
|
|
@@ -4210,6 +4606,17 @@ class Agent {
|
|
|
4210
4606
|
this.saveWorkspaceConversationState(true);
|
|
4211
4607
|
return this.goal.paused;
|
|
4212
4608
|
}
|
|
4609
|
+
/** Pause only in response to the user's explicit Stop action. */
|
|
4610
|
+
pauseGoalForUserInterrupt() {
|
|
4611
|
+
if (!this.goal)
|
|
4612
|
+
return false;
|
|
4613
|
+
if (!this.goal.paused) {
|
|
4614
|
+
this.goal.paused = true;
|
|
4615
|
+
this.status = 'goal_paused';
|
|
4616
|
+
this.saveWorkspaceConversationState(true);
|
|
4617
|
+
}
|
|
4618
|
+
return true;
|
|
4619
|
+
}
|
|
4213
4620
|
clearGoal() {
|
|
4214
4621
|
this.goal = null;
|
|
4215
4622
|
if (this.mode === 'goal')
|
|
@@ -4234,8 +4641,8 @@ class Agent {
|
|
|
4234
4641
|
canAutoContinueGoal() {
|
|
4235
4642
|
return this.goalContinuationGate ? this.goalContinuationGate() : true;
|
|
4236
4643
|
}
|
|
4237
|
-
claimGoalContinuationMessage() {
|
|
4238
|
-
if (this.mode !== 'goal' || !this.goal || this.goal.paused || !this.canAutoContinueGoal())
|
|
4644
|
+
claimGoalContinuationMessage(options) {
|
|
4645
|
+
if (this.mode !== 'goal' || !this.goal || this.goal.paused || (!options?.force && !this.canAutoContinueGoal()))
|
|
4239
4646
|
return null;
|
|
4240
4647
|
const maxGoalContinuations = Math.max(0, Math.floor(this.config.getNum('agent', 'goal_max_continuations') || 0));
|
|
4241
4648
|
if (maxGoalContinuations > 0 && this.goal.goalRounds >= maxGoalContinuations) {
|
|
@@ -6872,9 +7279,62 @@ class Agent {
|
|
|
6872
7279
|
if (this.isSubagentRuntime)
|
|
6873
7280
|
this.subagentContextPersist?.(this.history.map(message => ({ ...message })), this.lastCompression);
|
|
6874
7281
|
}
|
|
7282
|
+
compressionArchiveScopeKey() {
|
|
7283
|
+
if (this.isSubagentRuntime || !this.config.getBool('context', 'compression_archive_enabled'))
|
|
7284
|
+
return null;
|
|
7285
|
+
return this.workspaceConversationKey();
|
|
7286
|
+
}
|
|
7287
|
+
coldCompressionEntries() {
|
|
7288
|
+
const scopeKey = this.compressionArchiveScopeKey();
|
|
7289
|
+
if (!scopeKey)
|
|
7290
|
+
return [];
|
|
7291
|
+
try {
|
|
7292
|
+
const hotIds = new Set(this.compressionCache.map(entry => entry.id));
|
|
7293
|
+
return this.compressionHistoryArchive.activeEntries(scopeKey).filter(entry => !hotIds.has(entry.id));
|
|
7294
|
+
}
|
|
7295
|
+
catch {
|
|
7296
|
+
return [];
|
|
7297
|
+
}
|
|
7298
|
+
}
|
|
7299
|
+
archiveColdCompressionEntries(entries) {
|
|
7300
|
+
const scopeKey = this.compressionArchiveScopeKey();
|
|
7301
|
+
if (!scopeKey)
|
|
7302
|
+
return [];
|
|
7303
|
+
const failed = [];
|
|
7304
|
+
for (const entry of entries) {
|
|
7305
|
+
try {
|
|
7306
|
+
this.compressionHistoryArchive.archive(scopeKey, entry);
|
|
7307
|
+
}
|
|
7308
|
+
catch {
|
|
7309
|
+
// Prefer a temporarily oversized hot cache over irreversible history loss.
|
|
7310
|
+
failed.push(entry);
|
|
7311
|
+
}
|
|
7312
|
+
}
|
|
7313
|
+
return failed;
|
|
7314
|
+
}
|
|
7315
|
+
markColdCompressionRestored(id) {
|
|
7316
|
+
const scopeKey = this.compressionArchiveScopeKey();
|
|
7317
|
+
if (!scopeKey)
|
|
7318
|
+
return;
|
|
7319
|
+
try {
|
|
7320
|
+
this.compressionHistoryArchive.markRestored(scopeKey, id);
|
|
7321
|
+
}
|
|
7322
|
+
catch {
|
|
7323
|
+
// The restored context is already authoritative; archive bookkeeping is best-effort.
|
|
7324
|
+
}
|
|
7325
|
+
}
|
|
6875
7326
|
pushCompressionCacheEntry(summary, messages, model, fallback) {
|
|
6876
7327
|
if (!messages.length)
|
|
6877
7328
|
return;
|
|
7329
|
+
const scopeKey = this.compressionArchiveScopeKey();
|
|
7330
|
+
let archivedMaxId = 0;
|
|
7331
|
+
if (scopeKey) {
|
|
7332
|
+
try {
|
|
7333
|
+
archivedMaxId = this.compressionHistoryArchive.maxNumericId(scopeKey);
|
|
7334
|
+
}
|
|
7335
|
+
catch { }
|
|
7336
|
+
}
|
|
7337
|
+
this.nextCompressionCacheId = Math.max(this.nextCompressionCacheId, archivedMaxId + 1);
|
|
6878
7338
|
const foldedChars = messages.reduce((sum, message) => sum + (typeof message.content === 'string'
|
|
6879
7339
|
? message.content.length
|
|
6880
7340
|
: JSON.stringify(message.content || '').length), 0);
|
|
@@ -6891,7 +7351,9 @@ class Agent {
|
|
|
6891
7351
|
this.nextCompressionCacheId += 1;
|
|
6892
7352
|
const maxEntries = Math.max(0, Math.floor(this.config.getNum('context', 'compression_cache_max') || 8));
|
|
6893
7353
|
if (this.compressionCache.length > maxEntries) {
|
|
6894
|
-
|
|
7354
|
+
const evicted = this.compressionCache.slice(0, this.compressionCache.length - maxEntries);
|
|
7355
|
+
const failed = this.archiveColdCompressionEntries(evicted);
|
|
7356
|
+
this.compressionCache = [...failed, ...this.compressionCache.slice(this.compressionCache.length - maxEntries)];
|
|
6895
7357
|
}
|
|
6896
7358
|
this.saveWorkspaceConversationState(true);
|
|
6897
7359
|
}
|