@junctionpanel/server 0.1.83 → 0.1.85
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/server/client/daemon-client.d.ts +10 -0
- package/dist/server/client/daemon-client.d.ts.map +1 -1
- package/dist/server/client/daemon-client.js +58 -0
- package/dist/server/client/daemon-client.js.map +1 -1
- package/dist/server/server/agent/agent-checkpoint-storage.d.ts +126 -0
- package/dist/server/server/agent/agent-checkpoint-storage.d.ts.map +1 -0
- package/dist/server/server/agent/agent-checkpoint-storage.js +203 -0
- package/dist/server/server/agent/agent-checkpoint-storage.js.map +1 -0
- package/dist/server/server/agent/agent-manager.d.ts +16 -0
- package/dist/server/server/agent/agent-manager.d.ts.map +1 -1
- package/dist/server/server/agent/agent-manager.js +584 -12
- package/dist/server/server/agent/agent-manager.js.map +1 -1
- package/dist/server/server/agent/agent-projections.d.ts.map +1 -1
- package/dist/server/server/agent/agent-projections.js +5 -0
- package/dist/server/server/agent/agent-projections.js.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.d.ts +2 -2
- package/dist/server/server/agent/agent-sdk-types.d.ts.map +1 -1
- package/dist/server/server/agent/agent-sdk-types.js.map +1 -1
- package/dist/server/server/agent/agent-storage.d.ts +37 -37
- package/dist/server/server/agent/harness/context.d.ts +2 -1
- package/dist/server/server/agent/harness/context.d.ts.map +1 -1
- package/dist/server/server/agent/harness/context.js +106 -0
- package/dist/server/server/agent/harness/context.js.map +1 -1
- package/dist/server/server/agent/harness/memory.d.ts.map +1 -1
- package/dist/server/server/agent/harness/memory.js +38 -8
- package/dist/server/server/agent/harness/memory.js.map +1 -1
- package/dist/server/server/agent/harness/permission-policy.d.ts +10 -0
- package/dist/server/server/agent/harness/permission-policy.d.ts.map +1 -0
- package/dist/server/server/agent/harness/permission-policy.js +86 -0
- package/dist/server/server/agent/harness/permission-policy.js.map +1 -0
- package/dist/server/server/agent/harness/risk-classifier.d.ts +8 -0
- package/dist/server/server/agent/harness/risk-classifier.d.ts.map +1 -0
- package/dist/server/server/agent/harness/risk-classifier.js +73 -0
- package/dist/server/server/agent/harness/risk-classifier.js.map +1 -0
- package/dist/server/server/agent/harness/run-ledger.d.ts +21 -0
- package/dist/server/server/agent/harness/run-ledger.d.ts.map +1 -0
- package/dist/server/server/agent/harness/run-ledger.js +79 -0
- package/dist/server/server/agent/harness/run-ledger.js.map +1 -0
- package/dist/server/server/agent/harness/session-bundle.d.ts +13 -0
- package/dist/server/server/agent/harness/session-bundle.d.ts.map +1 -0
- package/dist/server/server/agent/harness/session-bundle.js +50 -0
- package/dist/server/server/agent/harness/session-bundle.js.map +1 -0
- package/dist/server/server/agent/harness/types.d.ts +150 -2
- package/dist/server/server/agent/harness/types.d.ts.map +1 -1
- package/dist/server/server/agent/harness/types.js +1 -1
- package/dist/server/server/agent/harness/types.js.map +1 -1
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts +2 -2
- package/dist/server/server/agent/providers/tool-call-detail-primitives.d.ts.map +1 -1
- package/dist/server/server/bootstrap.d.ts +1 -0
- package/dist/server/server/bootstrap.d.ts.map +1 -1
- package/dist/server/server/bootstrap.js +4 -0
- package/dist/server/server/bootstrap.js.map +1 -1
- package/dist/server/server/config.d.ts.map +1 -1
- package/dist/server/server/config.js +1 -0
- package/dist/server/server/config.js.map +1 -1
- package/dist/server/server/persisted-config.d.ts +6 -6
- package/dist/server/server/session.d.ts +2 -0
- package/dist/server/server/session.d.ts.map +1 -1
- package/dist/server/server/session.js +94 -0
- package/dist/server/server/session.js.map +1 -1
- package/dist/server/shared/messages.d.ts +9992 -7147
- package/dist/server/shared/messages.d.ts.map +1 -1
- package/dist/server/shared/messages.js +230 -0
- package/dist/server/shared/messages.js.map +1 -1
- package/dist/server/shared/switchboard.d.ts +1 -1
- package/dist/server/utils/worktree-metadata.d.ts +8 -8
- package/package.json +2 -2
|
@@ -9,6 +9,9 @@ import { buildPermissionRecoveryFingerprint } from "./agent-permission-fingerpri
|
|
|
9
9
|
import { isCodexPlanModeEnabled, normalizeCodexModeId, setCodexPlanModeEnabled, } from "./codex-config.js";
|
|
10
10
|
import { applyHarnessSystemPrompt, buildHarnessContext, compileHarnessSystemPrompt, getHarnessSessionExtra, mergeRuntimeInfoWithHarness, setHarnessSessionExtra, } from "./harness/context.js";
|
|
11
11
|
import { summarizeTurnFromTimeline, updateSessionState } from "./harness/memory.js";
|
|
12
|
+
import { createHarnessPermissionLedgerEntry, resolveHarnessPermissionLedger, } from "./harness/permission-policy.js";
|
|
13
|
+
import { getActiveHarnessRun, touchHarnessRunProgress, transitionHarnessRunLedger, } from "./harness/run-ledger.js";
|
|
14
|
+
import { accumulateHarnessCostState, createHarnessResumeState, ensureHarnessScratchpadDir, touchHarnessResumeState, } from "./harness/session-bundle.js";
|
|
12
15
|
import { JUNCTION_HARNESS_VERSION } from "./harness/types.js";
|
|
13
16
|
import { getPendingPlanReviewFingerprint, hasPendingPlanReview, } from "./pending-plan-review.js";
|
|
14
17
|
import { extractPermissionQuestionAnswersFromResponse, formatPermissionQuestionAnswers, isInteractiveQuestionPermission, } from "../../shared/permission-questions.js";
|
|
@@ -43,6 +46,36 @@ function extractGeminiFallbackPermissionUpdate(response) {
|
|
|
43
46
|
const persistedSummary = typeof fallback.persistedSummary === "string" ? fallback.persistedSummary.trim() : "";
|
|
44
47
|
return persistedSummary.length > 0 ? { kind, persistedSummary } : { kind };
|
|
45
48
|
}
|
|
49
|
+
function inferHarnessWorkerRole(detail) {
|
|
50
|
+
const haystack = `${detail.subAgentType ?? ""} ${detail.description ?? ""}`.toLowerCase();
|
|
51
|
+
if (haystack.includes("explore") || haystack.includes("research")) {
|
|
52
|
+
return "explore";
|
|
53
|
+
}
|
|
54
|
+
if (haystack.includes("plan")) {
|
|
55
|
+
return "plan";
|
|
56
|
+
}
|
|
57
|
+
if (haystack.includes("verify") || haystack.includes("test")) {
|
|
58
|
+
return "verify";
|
|
59
|
+
}
|
|
60
|
+
if (haystack.includes("implement") || haystack.includes("fix")) {
|
|
61
|
+
return "implement";
|
|
62
|
+
}
|
|
63
|
+
return "generic";
|
|
64
|
+
}
|
|
65
|
+
function mapTurnOutcomeToRunLifecycle(outcome) {
|
|
66
|
+
switch (outcome) {
|
|
67
|
+
case "provider_error":
|
|
68
|
+
return "error";
|
|
69
|
+
case "canceled":
|
|
70
|
+
return "interrupted";
|
|
71
|
+
case "completed":
|
|
72
|
+
case "needs_verification":
|
|
73
|
+
return "completed";
|
|
74
|
+
case "blocked_on_permission":
|
|
75
|
+
case "blocked_on_feedback":
|
|
76
|
+
return "waiting_permission";
|
|
77
|
+
}
|
|
78
|
+
}
|
|
46
79
|
function attachPersistenceCwd(handle, cwd) {
|
|
47
80
|
if (!handle) {
|
|
48
81
|
return null;
|
|
@@ -225,6 +258,7 @@ export class AgentManager {
|
|
|
225
258
|
: null;
|
|
226
259
|
this.idFactory = options?.idFactory ?? (() => randomUUID());
|
|
227
260
|
this.registry = options?.registry;
|
|
261
|
+
this.checkpointStorage = options?.checkpointStorage;
|
|
228
262
|
this.onAgentAttention = options?.onAgentAttention;
|
|
229
263
|
this.logger = options.logger.child({ module: "agent", component: "agent-manager" });
|
|
230
264
|
if (options?.clients) {
|
|
@@ -531,6 +565,7 @@ export class AgentManager {
|
|
|
531
565
|
return this.registerSession(session, normalizedConfig, resolvedAgentId, {
|
|
532
566
|
labels: options?.labels,
|
|
533
567
|
parentAgentId: options?.parentAgentId ?? null,
|
|
568
|
+
resumeSource: "new_session",
|
|
534
569
|
});
|
|
535
570
|
}
|
|
536
571
|
// Reconstruct an agent from provider persistence. Callers should explicitly
|
|
@@ -549,7 +584,7 @@ export class AgentManager {
|
|
|
549
584
|
: overrides;
|
|
550
585
|
const client = this.requireClient(handle.provider);
|
|
551
586
|
const session = await client.resumeSession(handle, resumeOverrides);
|
|
552
|
-
return this.registerSession(session, normalizedConfig, resolvedAgentId, options);
|
|
587
|
+
return this.registerSession(session, normalizedConfig, resolvedAgentId, { ...options, resumeSource: "provider_resume" });
|
|
553
588
|
}
|
|
554
589
|
// Hot-reload an active agent session with config overrides while preserving
|
|
555
590
|
// in-memory timeline state.
|
|
@@ -613,6 +648,7 @@ export class AgentManager {
|
|
|
613
648
|
activeTurnStartedAt: preservedActiveTurnStartedAt,
|
|
614
649
|
lastError: preservedLastError,
|
|
615
650
|
attention: preservedAttention,
|
|
651
|
+
resumeSource: handle ? "provider_resume" : "new_session",
|
|
616
652
|
});
|
|
617
653
|
}
|
|
618
654
|
async closeAgent(agentId) {
|
|
@@ -1044,13 +1080,15 @@ export class AgentManager {
|
|
|
1044
1080
|
}
|
|
1045
1081
|
}
|
|
1046
1082
|
await agent.session.respondToPermission(requestId, response);
|
|
1047
|
-
|
|
1048
|
-
this.clearAttentionForUserEngagement(agent);
|
|
1049
|
-
this.recordPermissionResolutionTimelineItem(agent, requestId, response, {
|
|
1083
|
+
this.resolvePendingPermission(agent, requestId, response, {
|
|
1050
1084
|
provider: agent.provider,
|
|
1051
|
-
|
|
1085
|
+
dispatchEvent: false,
|
|
1052
1086
|
dispatchTimeline: true,
|
|
1087
|
+
syncHarness: true,
|
|
1088
|
+
dispatchHarnessEvent: false,
|
|
1089
|
+
transitionRunToStreaming: getActiveHarnessRun(getHarnessSessionExtra(agent.config)?.runLedger) !== null,
|
|
1053
1090
|
});
|
|
1091
|
+
this.clearAttentionForUserEngagement(agent);
|
|
1054
1092
|
// Update currentModeId - the session may have changed mode internally
|
|
1055
1093
|
// (e.g., plan approval changes mode from "plan" to "acceptEdits")
|
|
1056
1094
|
try {
|
|
@@ -1122,6 +1160,9 @@ export class AgentManager {
|
|
|
1122
1160
|
provider: agent.provider,
|
|
1123
1161
|
dispatchEvent: true,
|
|
1124
1162
|
dispatchTimeline: true,
|
|
1163
|
+
syncHarness: true,
|
|
1164
|
+
dispatchHarnessEvent: true,
|
|
1165
|
+
transitionRunToStreaming: false,
|
|
1125
1166
|
});
|
|
1126
1167
|
}
|
|
1127
1168
|
this.emitState(agent);
|
|
@@ -1170,7 +1211,14 @@ export class AgentManager {
|
|
|
1170
1211
|
for (const [requestId, request] of agent.pendingPermissions) {
|
|
1171
1212
|
const fingerprint = buildPermissionRecoveryFingerprint(request);
|
|
1172
1213
|
if (requestIds.has(request.id) || requestFingerprints.has(fingerprint)) {
|
|
1173
|
-
|
|
1214
|
+
this.resolvePendingPermission(agent, requestId, { behavior: "deny", message: "Approval request expired while the daemon was offline." }, {
|
|
1215
|
+
provider: agent.provider,
|
|
1216
|
+
dispatchEvent: false,
|
|
1217
|
+
dispatchTimeline: false,
|
|
1218
|
+
syncHarness: true,
|
|
1219
|
+
dispatchHarnessEvent: false,
|
|
1220
|
+
transitionRunToStreaming: false,
|
|
1221
|
+
});
|
|
1174
1222
|
removed += 1;
|
|
1175
1223
|
}
|
|
1176
1224
|
}
|
|
@@ -1407,6 +1455,18 @@ export class AgentManager {
|
|
|
1407
1455
|
internal: config.internal ?? false,
|
|
1408
1456
|
labels: options?.labels ?? {},
|
|
1409
1457
|
};
|
|
1458
|
+
const registeredAt = (options?.updatedAt ?? options?.createdAt ?? now).toISOString();
|
|
1459
|
+
const existingHarnessExtra = getHarnessSessionExtra(config);
|
|
1460
|
+
setHarnessSessionExtra(config, {
|
|
1461
|
+
scratchpadDir: existingHarnessExtra?.scratchpadDir ?? ensureHarnessScratchpadDir(resolvedAgentId),
|
|
1462
|
+
resumeState: existingHarnessExtra?.resumeState
|
|
1463
|
+
? touchHarnessResumeState(existingHarnessExtra.resumeState, {
|
|
1464
|
+
status: existingHarnessExtra.resumeState.status === "checkpoint_restored" ? "checkpoint_restored" : "live",
|
|
1465
|
+
source: existingHarnessExtra.resumeState.source,
|
|
1466
|
+
timestamp: registeredAt,
|
|
1467
|
+
})
|
|
1468
|
+
: createHarnessResumeState(options?.resumeSource ?? "new_session", registeredAt),
|
|
1469
|
+
});
|
|
1410
1470
|
this.agents.set(resolvedAgentId, managed);
|
|
1411
1471
|
// Initialize previousStatus to track transitions
|
|
1412
1472
|
this.previousStatuses.set(resolvedAgentId, managed.lifecycle);
|
|
@@ -1606,6 +1666,11 @@ export class AgentManager {
|
|
|
1606
1666
|
agent.lastUserMessageAt = new Date();
|
|
1607
1667
|
this.emitState(agent);
|
|
1608
1668
|
}
|
|
1669
|
+
if (!options?.fromHistory &&
|
|
1670
|
+
event.item.type !== "permission_request" &&
|
|
1671
|
+
event.item.type !== "permission_resolution") {
|
|
1672
|
+
this.touchActiveHarnessRunProgress(agent);
|
|
1673
|
+
}
|
|
1609
1674
|
break;
|
|
1610
1675
|
case "turn_completed":
|
|
1611
1676
|
this.pendingUserInterruptAgents.delete(agent.id);
|
|
@@ -1615,7 +1680,20 @@ export class AgentManager {
|
|
|
1615
1680
|
let shouldEmitState = pendingRefresh.changed;
|
|
1616
1681
|
agent.lastUsage = event.usage;
|
|
1617
1682
|
agent.lastError = undefined;
|
|
1618
|
-
|
|
1683
|
+
if (!options?.fromHistory) {
|
|
1684
|
+
this.finalizeHarnessTurn(agent, "completed");
|
|
1685
|
+
const timestamp = new Date().toISOString();
|
|
1686
|
+
const harnessExtra = getHarnessSessionExtra(agent.config);
|
|
1687
|
+
setHarnessSessionExtra(agent.config, {
|
|
1688
|
+
costState: accumulateHarnessCostState(harnessExtra?.costState, event.usage, timestamp),
|
|
1689
|
+
resumeState: touchHarnessResumeState(harnessExtra?.resumeState, {
|
|
1690
|
+
status: "live",
|
|
1691
|
+
source: harnessExtra?.resumeState?.source ?? "snapshot",
|
|
1692
|
+
timestamp,
|
|
1693
|
+
recoveryHint: null,
|
|
1694
|
+
}),
|
|
1695
|
+
});
|
|
1696
|
+
}
|
|
1619
1697
|
if (!options?.fromHistory) {
|
|
1620
1698
|
const turnSummaryItem = this.buildTurnSummaryTimelineItem(agent, event.usage, event.modelId, agent.activeTurnStartedAt);
|
|
1621
1699
|
if (turnSummaryItem) {
|
|
@@ -1647,12 +1725,24 @@ export class AgentManager {
|
|
|
1647
1725
|
}
|
|
1648
1726
|
agent.activeTurnStartedAt = null;
|
|
1649
1727
|
agent.lastError = event.error;
|
|
1650
|
-
|
|
1728
|
+
if (!options?.fromHistory) {
|
|
1729
|
+
this.finalizeHarnessTurn(agent, "provider_error");
|
|
1730
|
+
this.recordHarnessEvent(agent, {
|
|
1731
|
+
kind: "recovery_hint_emitted",
|
|
1732
|
+
summary: "The provider run failed. Review the error and retry or resume the task.",
|
|
1733
|
+
details: {
|
|
1734
|
+
error: event.error,
|
|
1735
|
+
},
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1651
1738
|
for (const [requestId] of agent.pendingPermissions) {
|
|
1652
1739
|
this.resolvePendingPermission(agent, requestId, { behavior: "deny", message: "Turn failed" }, {
|
|
1653
1740
|
provider: event.provider,
|
|
1654
1741
|
dispatchEvent: !options?.fromHistory,
|
|
1655
1742
|
dispatchTimeline: !options?.fromHistory,
|
|
1743
|
+
syncHarness: !options?.fromHistory,
|
|
1744
|
+
dispatchHarnessEvent: !options?.fromHistory,
|
|
1745
|
+
transitionRunToStreaming: false,
|
|
1656
1746
|
});
|
|
1657
1747
|
}
|
|
1658
1748
|
this.emitState(agent);
|
|
@@ -1670,13 +1760,28 @@ export class AgentManager {
|
|
|
1670
1760
|
}
|
|
1671
1761
|
agent.activeTurnStartedAt = null;
|
|
1672
1762
|
agent.lastError = undefined;
|
|
1673
|
-
|
|
1763
|
+
if (!options?.fromHistory) {
|
|
1764
|
+
this.finalizeHarnessTurn(agent, "canceled");
|
|
1765
|
+
const timestamp = new Date().toISOString();
|
|
1766
|
+
const harnessExtra = getHarnessSessionExtra(agent.config);
|
|
1767
|
+
setHarnessSessionExtra(agent.config, {
|
|
1768
|
+
resumeState: touchHarnessResumeState(harnessExtra?.resumeState, {
|
|
1769
|
+
status: "live",
|
|
1770
|
+
source: harnessExtra?.resumeState?.source ?? "snapshot",
|
|
1771
|
+
timestamp,
|
|
1772
|
+
recoveryHint: "The run was interrupted before completion.",
|
|
1773
|
+
}),
|
|
1774
|
+
});
|
|
1775
|
+
}
|
|
1674
1776
|
if (!pendingRefresh.hasPendingPermissions) {
|
|
1675
1777
|
for (const requestId of pendingRequestIds) {
|
|
1676
1778
|
this.resolvePendingPermission(agent, requestId, { behavior: "deny", message: "Interrupted" }, {
|
|
1677
1779
|
provider: event.provider,
|
|
1678
1780
|
dispatchEvent: !options?.fromHistory,
|
|
1679
1781
|
dispatchTimeline: !options?.fromHistory,
|
|
1782
|
+
syncHarness: !options?.fromHistory,
|
|
1783
|
+
dispatchHarnessEvent: !options?.fromHistory,
|
|
1784
|
+
transitionRunToStreaming: false,
|
|
1680
1785
|
});
|
|
1681
1786
|
}
|
|
1682
1787
|
}
|
|
@@ -1700,12 +1805,22 @@ export class AgentManager {
|
|
|
1700
1805
|
if (!agent.pendingRun) {
|
|
1701
1806
|
agent.lifecycle = "running";
|
|
1702
1807
|
}
|
|
1808
|
+
if (!options?.fromHistory) {
|
|
1809
|
+
this.transitionHarnessRunState(agent, {
|
|
1810
|
+
lifecycle: "streaming",
|
|
1811
|
+
summary: "Run is streaming.",
|
|
1812
|
+
turnStartSeq: getHarnessSessionExtra(agent.config)?.currentTurn?.turnStartSeq ?? null,
|
|
1813
|
+
});
|
|
1814
|
+
}
|
|
1703
1815
|
this.emitState(agent);
|
|
1704
1816
|
shouldPersistSnapshot = true;
|
|
1705
1817
|
break;
|
|
1706
1818
|
case "permission_requested":
|
|
1707
1819
|
{
|
|
1708
1820
|
agent.pendingPermissions.set(event.request.id, event.request);
|
|
1821
|
+
if (!options?.fromHistory) {
|
|
1822
|
+
this.recordHarnessPermissionRequest(agent, event.request, true);
|
|
1823
|
+
}
|
|
1709
1824
|
this.recordPermissionRequestTimelineItem(agent, event.request, {
|
|
1710
1825
|
provider: event.provider,
|
|
1711
1826
|
dispatchTimeline: !options?.fromHistory,
|
|
@@ -1737,6 +1852,9 @@ export class AgentManager {
|
|
|
1737
1852
|
provider: event.provider,
|
|
1738
1853
|
dispatchEvent: false,
|
|
1739
1854
|
dispatchTimeline: !options?.fromHistory,
|
|
1855
|
+
syncHarness: !options?.fromHistory,
|
|
1856
|
+
dispatchHarnessEvent: !options?.fromHistory,
|
|
1857
|
+
transitionRunToStreaming: !options?.fromHistory,
|
|
1740
1858
|
});
|
|
1741
1859
|
this.emitState(agent);
|
|
1742
1860
|
shouldPersistSnapshot = true;
|
|
@@ -1863,6 +1981,12 @@ export class AgentManager {
|
|
|
1863
1981
|
}
|
|
1864
1982
|
resolvePendingPermission(agent, requestId, resolution, options) {
|
|
1865
1983
|
const pendingRequest = agent.pendingPermissions.get(requestId) ?? null;
|
|
1984
|
+
if (options.syncHarness) {
|
|
1985
|
+
this.recordHarnessPermissionResolution(agent, requestId, resolution, {
|
|
1986
|
+
dispatchEvent: options.dispatchHarnessEvent ?? false,
|
|
1987
|
+
transitionRunToStreaming: options.transitionRunToStreaming ?? false,
|
|
1988
|
+
});
|
|
1989
|
+
}
|
|
1866
1990
|
agent.pendingPermissions.delete(requestId);
|
|
1867
1991
|
const row = this.recordPermissionResolutionTimelineItem(agent, requestId, resolution, {
|
|
1868
1992
|
provider: options.provider,
|
|
@@ -2174,6 +2298,261 @@ export class AgentManager {
|
|
|
2174
2298
|
},
|
|
2175
2299
|
};
|
|
2176
2300
|
}
|
|
2301
|
+
recordHarnessEvent(agent, input) {
|
|
2302
|
+
const item = {
|
|
2303
|
+
type: "harness_event",
|
|
2304
|
+
kind: input.kind,
|
|
2305
|
+
summary: input.summary,
|
|
2306
|
+
...(input.checkpointId !== undefined ? { checkpointId: input.checkpointId } : {}),
|
|
2307
|
+
...(input.verificationState !== undefined
|
|
2308
|
+
? { verificationState: input.verificationState }
|
|
2309
|
+
: {}),
|
|
2310
|
+
...(input.trustState !== undefined ? { trustState: input.trustState } : {}),
|
|
2311
|
+
...(input.workerId !== undefined ? { workerId: input.workerId } : {}),
|
|
2312
|
+
...(input.details ? { details: input.details } : {}),
|
|
2313
|
+
};
|
|
2314
|
+
const row = this.recordTimeline(agent, item);
|
|
2315
|
+
this.dispatchStream(agent.id, { type: "timeline", provider: agent.provider, item }, {
|
|
2316
|
+
seq: row.seq,
|
|
2317
|
+
epoch: this.ensureTimelineState(agent).epoch,
|
|
2318
|
+
});
|
|
2319
|
+
}
|
|
2320
|
+
transitionHarnessRunState(agent, input) {
|
|
2321
|
+
const timestamp = new Date().toISOString();
|
|
2322
|
+
const current = getHarnessSessionExtra(agent.config);
|
|
2323
|
+
const previousRun = getActiveHarnessRun(current?.runLedger);
|
|
2324
|
+
const { ledger, run } = transitionHarnessRunLedger(current?.runLedger, {
|
|
2325
|
+
lifecycle: input.lifecycle,
|
|
2326
|
+
timestamp,
|
|
2327
|
+
idFactory: this.idFactory,
|
|
2328
|
+
owner: agent.internal ? "background" : "foreground",
|
|
2329
|
+
summary: input.summary,
|
|
2330
|
+
turnStartSeq: input.turnStartSeq,
|
|
2331
|
+
turnEndSeq: input.turnEndSeq,
|
|
2332
|
+
permissionRequestId: input.permissionRequestId,
|
|
2333
|
+
correlationId: agent.persistence?.sessionId ?? agent.runtimeInfo?.sessionId ?? null,
|
|
2334
|
+
modelId: agent.runtimeInfo?.model ?? agent.config.model ?? null,
|
|
2335
|
+
progress: input.lifecycle === "streaming",
|
|
2336
|
+
});
|
|
2337
|
+
setHarnessSessionExtra(agent.config, {
|
|
2338
|
+
runLedger: ledger,
|
|
2339
|
+
});
|
|
2340
|
+
if (input.dispatchEvent !== false &&
|
|
2341
|
+
(previousRun?.lifecycle !== run.lifecycle ||
|
|
2342
|
+
previousRun?.permissionRequestId !== run.permissionRequestId)) {
|
|
2343
|
+
this.recordHarnessEvent(agent, {
|
|
2344
|
+
kind: "run_state_changed",
|
|
2345
|
+
summary: input.summary ?? `Run is now ${run.lifecycle}.`,
|
|
2346
|
+
details: {
|
|
2347
|
+
runId: run.id,
|
|
2348
|
+
lifecycle: run.lifecycle,
|
|
2349
|
+
permissionRequestId: run.permissionRequestId,
|
|
2350
|
+
},
|
|
2351
|
+
});
|
|
2352
|
+
}
|
|
2353
|
+
}
|
|
2354
|
+
touchActiveHarnessRunProgress(agent) {
|
|
2355
|
+
const current = getHarnessSessionExtra(agent.config);
|
|
2356
|
+
const nextLedger = touchHarnessRunProgress(current?.runLedger, new Date().toISOString());
|
|
2357
|
+
if (!nextLedger || nextLedger === current?.runLedger) {
|
|
2358
|
+
return;
|
|
2359
|
+
}
|
|
2360
|
+
setHarnessSessionExtra(agent.config, {
|
|
2361
|
+
runLedger: nextLedger,
|
|
2362
|
+
});
|
|
2363
|
+
}
|
|
2364
|
+
recordHarnessPermissionRequest(agent, request, dispatchEvent) {
|
|
2365
|
+
const timestamp = new Date().toISOString();
|
|
2366
|
+
const current = getHarnessSessionExtra(agent.config);
|
|
2367
|
+
const existing = (current?.permissionLedger ?? []).filter((entry) => entry.requestId !== request.id);
|
|
2368
|
+
const nextEntry = createHarnessPermissionLedgerEntry(request, timestamp);
|
|
2369
|
+
setHarnessSessionExtra(agent.config, {
|
|
2370
|
+
permissionLedger: [nextEntry, ...existing].slice(0, 50),
|
|
2371
|
+
});
|
|
2372
|
+
this.transitionHarnessRunState(agent, {
|
|
2373
|
+
lifecycle: "waiting_permission",
|
|
2374
|
+
summary: `Waiting for permission: ${request.name}.`,
|
|
2375
|
+
permissionRequestId: request.id,
|
|
2376
|
+
dispatchEvent,
|
|
2377
|
+
});
|
|
2378
|
+
if (dispatchEvent) {
|
|
2379
|
+
this.recordHarnessEvent(agent, {
|
|
2380
|
+
kind: "permission_enqueued",
|
|
2381
|
+
summary: `Queued ${nextEntry.risk} permission request for ${request.name}.`,
|
|
2382
|
+
details: {
|
|
2383
|
+
requestId: request.id,
|
|
2384
|
+
risk: nextEntry.risk,
|
|
2385
|
+
fingerprint: nextEntry.fingerprint,
|
|
2386
|
+
kind: request.kind,
|
|
2387
|
+
},
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
}
|
|
2391
|
+
recordHarnessPermissionResolution(agent, requestId, resolution, options) {
|
|
2392
|
+
const timestamp = new Date().toISOString();
|
|
2393
|
+
const current = getHarnessSessionExtra(agent.config);
|
|
2394
|
+
const nextLedger = resolveHarnessPermissionLedger(current?.permissionLedger, requestId, resolution, timestamp);
|
|
2395
|
+
const resolvedEntry = nextLedger.find((entry) => entry.requestId === requestId) ?? null;
|
|
2396
|
+
setHarnessSessionExtra(agent.config, {
|
|
2397
|
+
permissionLedger: nextLedger,
|
|
2398
|
+
});
|
|
2399
|
+
if (options.dispatchEvent && resolvedEntry && agent.lifecycle !== "closed") {
|
|
2400
|
+
this.recordHarnessEvent(agent, {
|
|
2401
|
+
kind: "permission_resolved",
|
|
2402
|
+
summary: `Permission ${resolvedEntry.status} for ${resolvedEntry.toolName}.`,
|
|
2403
|
+
details: {
|
|
2404
|
+
requestId,
|
|
2405
|
+
risk: resolvedEntry.risk,
|
|
2406
|
+
status: resolvedEntry.status,
|
|
2407
|
+
},
|
|
2408
|
+
});
|
|
2409
|
+
if (resolvedEntry.recoveryHint && resolvedEntry.status !== "allowed") {
|
|
2410
|
+
this.recordHarnessEvent(agent, {
|
|
2411
|
+
kind: "recovery_hint_emitted",
|
|
2412
|
+
summary: resolvedEntry.recoveryHint,
|
|
2413
|
+
details: {
|
|
2414
|
+
requestId,
|
|
2415
|
+
status: resolvedEntry.status,
|
|
2416
|
+
},
|
|
2417
|
+
});
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2420
|
+
if (options.transitionRunToStreaming && agent.lifecycle !== "closed") {
|
|
2421
|
+
this.transitionHarnessRunState(agent, {
|
|
2422
|
+
lifecycle: "streaming",
|
|
2423
|
+
summary: resolvedEntry != null
|
|
2424
|
+
? `Permission ${resolvedEntry.status} for ${resolvedEntry.toolName}.`
|
|
2425
|
+
: "Permission resolved.",
|
|
2426
|
+
permissionRequestId: null,
|
|
2427
|
+
dispatchEvent: false,
|
|
2428
|
+
});
|
|
2429
|
+
}
|
|
2430
|
+
}
|
|
2431
|
+
upsertCheckpointSummary(existing, summary) {
|
|
2432
|
+
return [summary, ...(existing ?? []).filter((entry) => entry.id !== summary.id)].slice(0, 50);
|
|
2433
|
+
}
|
|
2434
|
+
deriveHarnessWorkers(agent, turnRows, objective, timestamp) {
|
|
2435
|
+
const workers = {
|
|
2436
|
+
...(getHarnessSessionExtra(agent.config)?.workers ?? {}),
|
|
2437
|
+
};
|
|
2438
|
+
for (const row of turnRows) {
|
|
2439
|
+
const item = row.item;
|
|
2440
|
+
if (item.type !== "tool_call" || item.detail.type !== "sub_agent") {
|
|
2441
|
+
continue;
|
|
2442
|
+
}
|
|
2443
|
+
const existing = workers[item.callId];
|
|
2444
|
+
const nextStatus = item.status === "running"
|
|
2445
|
+
? "running"
|
|
2446
|
+
: item.status === "completed"
|
|
2447
|
+
? existing?.status === "integrated"
|
|
2448
|
+
? "integrated"
|
|
2449
|
+
: "completed"
|
|
2450
|
+
: "failed";
|
|
2451
|
+
workers[item.callId] = {
|
|
2452
|
+
id: item.callId,
|
|
2453
|
+
provider: agent.provider,
|
|
2454
|
+
threadId: null,
|
|
2455
|
+
parentAgentId: agent.id,
|
|
2456
|
+
role: inferHarnessWorkerRole(item.detail),
|
|
2457
|
+
kind: "provider_subagent",
|
|
2458
|
+
purpose: item.detail.description?.trim() ||
|
|
2459
|
+
item.detail.subAgentType?.trim() ||
|
|
2460
|
+
"Sub-agent task",
|
|
2461
|
+
owner: objective,
|
|
2462
|
+
status: nextStatus,
|
|
2463
|
+
queueState: nextStatus === "running" ? "active" : "completed",
|
|
2464
|
+
outputSummary: item.detail.actions[item.detail.actions.length - 1]?.summary ??
|
|
2465
|
+
item.detail.description ??
|
|
2466
|
+
existing?.outputSummary ??
|
|
2467
|
+
null,
|
|
2468
|
+
changedFiles: existing?.changedFiles ?? [],
|
|
2469
|
+
verificationState: existing?.verificationState ?? "not_needed",
|
|
2470
|
+
startedAt: existing?.startedAt ?? row.timestamp,
|
|
2471
|
+
integratedAt: existing?.integratedAt ?? null,
|
|
2472
|
+
completedAt: nextStatus === "completed" || nextStatus === "integrated" || nextStatus === "failed"
|
|
2473
|
+
? timestamp
|
|
2474
|
+
: existing?.completedAt ?? null,
|
|
2475
|
+
sessionId: existing?.sessionId ?? null,
|
|
2476
|
+
transcriptPath: existing?.transcriptPath ?? null,
|
|
2477
|
+
outputPath: existing?.outputPath ?? null,
|
|
2478
|
+
rolloutPath: existing?.rolloutPath ?? null,
|
|
2479
|
+
recentActions: item.detail.actions.map((action) => ({ ...action })),
|
|
2480
|
+
permissionState: existing?.permissionState ?? "none",
|
|
2481
|
+
lastPermissionRequestId: existing?.lastPermissionRequestId ?? null,
|
|
2482
|
+
lastPermissionToolName: existing?.lastPermissionToolName ?? null,
|
|
2483
|
+
error: item.status === "failed"
|
|
2484
|
+
? typeof item.error === "object" && item.error !== null && "message" in item.error
|
|
2485
|
+
? String(item.error.message ?? "Worker failed")
|
|
2486
|
+
: String(item.error)
|
|
2487
|
+
: null,
|
|
2488
|
+
progress: {
|
|
2489
|
+
toolCallCount: item.detail.actions.length,
|
|
2490
|
+
lastActivityAt: row.timestamp,
|
|
2491
|
+
latestSummary: item.detail.actions[item.detail.actions.length - 1]?.summary ??
|
|
2492
|
+
item.detail.description ??
|
|
2493
|
+
existing?.progress?.latestSummary ??
|
|
2494
|
+
null,
|
|
2495
|
+
},
|
|
2496
|
+
lastUpdatedAt: timestamp,
|
|
2497
|
+
};
|
|
2498
|
+
}
|
|
2499
|
+
return workers;
|
|
2500
|
+
}
|
|
2501
|
+
async captureCheckpoint(agent, input) {
|
|
2502
|
+
if (!this.checkpointStorage) {
|
|
2503
|
+
return null;
|
|
2504
|
+
}
|
|
2505
|
+
const harness = input.harnessSnapshot ?? getHarnessSessionExtra(agent.config);
|
|
2506
|
+
if (!harness) {
|
|
2507
|
+
return null;
|
|
2508
|
+
}
|
|
2509
|
+
try {
|
|
2510
|
+
return await this.checkpointStorage.capture({
|
|
2511
|
+
agentId: agent.id,
|
|
2512
|
+
provider: agent.provider,
|
|
2513
|
+
cwd: agent.cwd,
|
|
2514
|
+
id: input.id,
|
|
2515
|
+
kind: input.kind,
|
|
2516
|
+
objective: input.objective,
|
|
2517
|
+
summary: input.summary,
|
|
2518
|
+
changedFiles: input.changedFiles,
|
|
2519
|
+
turnStartSeq: input.turnStartSeq,
|
|
2520
|
+
turnEndSeq: input.turnEndSeq,
|
|
2521
|
+
harness,
|
|
2522
|
+
});
|
|
2523
|
+
}
|
|
2524
|
+
catch (error) {
|
|
2525
|
+
this.logger.warn({ err: error, agentId: agent.id, checkpointId: input.id }, "Failed to capture Junction harness checkpoint");
|
|
2526
|
+
return null;
|
|
2527
|
+
}
|
|
2528
|
+
}
|
|
2529
|
+
publishCapturedCheckpoint(agent, summary, input) {
|
|
2530
|
+
const current = getHarnessSessionExtra(agent.config);
|
|
2531
|
+
if (!current) {
|
|
2532
|
+
return;
|
|
2533
|
+
}
|
|
2534
|
+
const extraPatch = input.onSetExtra?.(current);
|
|
2535
|
+
setHarnessSessionExtra(agent.config, {
|
|
2536
|
+
...extraPatch,
|
|
2537
|
+
checkpoints: this.upsertCheckpointSummary(current.checkpoints, summary),
|
|
2538
|
+
lastCheckpointId: summary.id,
|
|
2539
|
+
});
|
|
2540
|
+
this.recordHarnessEvent(agent, {
|
|
2541
|
+
kind: "checkpoint_created",
|
|
2542
|
+
summary: input.eventSummary,
|
|
2543
|
+
checkpointId: summary.id,
|
|
2544
|
+
details: input.details,
|
|
2545
|
+
});
|
|
2546
|
+
this.touchUpdatedAt(agent);
|
|
2547
|
+
this.emitState(agent);
|
|
2548
|
+
const persistTask = this.persistSnapshot(agent).catch((error) => {
|
|
2549
|
+
this.logger.warn({ err: error, agentId: agent.id, checkpointId: summary.id }, "Failed to persist snapshot after publishing Junction harness checkpoint");
|
|
2550
|
+
});
|
|
2551
|
+
this.trackBackgroundTask(persistTask);
|
|
2552
|
+
}
|
|
2553
|
+
isLiveAgent(agent) {
|
|
2554
|
+
return this.agents.get(agent.id) === agent;
|
|
2555
|
+
}
|
|
2177
2556
|
async prepareHarnessForRun(agent, prompt) {
|
|
2178
2557
|
const providerInstructionStrategy = resolveProviderInstructionStrategy(agent.provider);
|
|
2179
2558
|
try {
|
|
@@ -2182,21 +2561,79 @@ export class AgentManager {
|
|
|
2182
2561
|
prompt,
|
|
2183
2562
|
providerInstructionStrategy,
|
|
2184
2563
|
});
|
|
2564
|
+
const previousHarnessExtra = getHarnessSessionExtra(agent.config);
|
|
2565
|
+
const turnStartSeq = this.ensureTimelineState(agent).nextSeq;
|
|
2185
2566
|
const compiledSystemPrompt = compileHarnessSystemPrompt(context);
|
|
2186
2567
|
applyHarnessSystemPrompt(agent.config, compiledSystemPrompt);
|
|
2568
|
+
const startedAt = new Date().toISOString();
|
|
2569
|
+
const scratchpadDir = previousHarnessExtra?.scratchpadDir ?? ensureHarnessScratchpadDir(agent.id);
|
|
2187
2570
|
setHarnessSessionExtra(agent.config, {
|
|
2188
2571
|
workspaceSynopsis: context.workspaceSynopsis,
|
|
2189
2572
|
trustState: context.trustState,
|
|
2190
2573
|
contextLayers: context.promptLayers.map((layer) => layer.id),
|
|
2191
2574
|
providerInstructionStrategy,
|
|
2192
2575
|
harnessVersion: JUNCTION_HARNESS_VERSION,
|
|
2576
|
+
scratchpadDir,
|
|
2577
|
+
resumeState: previousHarnessExtra?.resumeState
|
|
2578
|
+
? touchHarnessResumeState(previousHarnessExtra.resumeState, {
|
|
2579
|
+
status: "live",
|
|
2580
|
+
source: previousHarnessExtra.resumeState.source,
|
|
2581
|
+
timestamp: startedAt,
|
|
2582
|
+
recoveryHint: null,
|
|
2583
|
+
})
|
|
2584
|
+
: createHarnessResumeState("new_session", startedAt),
|
|
2193
2585
|
currentTurn: {
|
|
2194
2586
|
classification: context.classification,
|
|
2195
2587
|
objective: context.objective,
|
|
2196
|
-
turnStartSeq
|
|
2197
|
-
startedAt
|
|
2588
|
+
turnStartSeq,
|
|
2589
|
+
startedAt,
|
|
2198
2590
|
},
|
|
2199
2591
|
});
|
|
2592
|
+
if (previousHarnessExtra?.trustState !== context.trustState) {
|
|
2593
|
+
this.recordHarnessEvent(agent, {
|
|
2594
|
+
kind: "trust_changed",
|
|
2595
|
+
summary: `Trust state is now ${context.trustState}.`,
|
|
2596
|
+
trustState: context.trustState,
|
|
2597
|
+
});
|
|
2598
|
+
}
|
|
2599
|
+
if (this.checkpointStorage &&
|
|
2600
|
+
(context.classification === "implement" ||
|
|
2601
|
+
context.classification === "debug" ||
|
|
2602
|
+
context.classification === "review")) {
|
|
2603
|
+
const checkpointId = this.idFactory();
|
|
2604
|
+
const record = await this.captureCheckpoint(agent, {
|
|
2605
|
+
id: checkpointId,
|
|
2606
|
+
kind: "pre_mutation",
|
|
2607
|
+
objective: context.objective,
|
|
2608
|
+
summary: "Pre-mutation checkpoint",
|
|
2609
|
+
changedFiles: [],
|
|
2610
|
+
turnStartSeq,
|
|
2611
|
+
turnEndSeq: turnStartSeq,
|
|
2612
|
+
});
|
|
2613
|
+
if (record) {
|
|
2614
|
+
this.publishCapturedCheckpoint(agent, this.checkpointStorage.toSummary(record), {
|
|
2615
|
+
eventSummary: "Created a pre-mutation checkpoint.",
|
|
2616
|
+
details: {
|
|
2617
|
+
checkpointKind: "pre_mutation",
|
|
2618
|
+
turnStartSeq,
|
|
2619
|
+
},
|
|
2620
|
+
onSetExtra: (current) => ({
|
|
2621
|
+
currentTurn: current?.currentTurn
|
|
2622
|
+
? {
|
|
2623
|
+
...current.currentTurn,
|
|
2624
|
+
preMutationCheckpointId: checkpointId,
|
|
2625
|
+
}
|
|
2626
|
+
: {
|
|
2627
|
+
classification: context.classification,
|
|
2628
|
+
objective: context.objective,
|
|
2629
|
+
turnStartSeq,
|
|
2630
|
+
startedAt,
|
|
2631
|
+
preMutationCheckpointId: checkpointId,
|
|
2632
|
+
},
|
|
2633
|
+
}),
|
|
2634
|
+
});
|
|
2635
|
+
}
|
|
2636
|
+
}
|
|
2200
2637
|
}
|
|
2201
2638
|
catch (error) {
|
|
2202
2639
|
this.logger.warn({ err: error, agentId: agent.id, provider: agent.provider }, "Failed to prepare Junction harness context; continuing with provider defaults");
|
|
@@ -2239,21 +2676,156 @@ export class AgentManager {
|
|
|
2239
2676
|
turnEndSeq: turnRows.length > 0 ? turnRows[turnRows.length - 1].seq : currentTurn.turnStartSeq,
|
|
2240
2677
|
timestamp,
|
|
2241
2678
|
});
|
|
2679
|
+
const nextWorkers = this.deriveHarnessWorkers(agent, turnRows, currentTurn.objective, timestamp);
|
|
2242
2680
|
const sessionState = updateSessionState(harnessExtra?.sessionState ?? null, turnSummary, {
|
|
2243
2681
|
modeId: agent.currentModeId ?? null,
|
|
2244
2682
|
modelId: agent.runtimeInfo?.model ?? agent.config.model ?? null,
|
|
2245
2683
|
thinkingOptionId: agent.runtimeInfo?.thinkingOptionId ?? agent.config.thinkingOptionId ?? null,
|
|
2246
2684
|
});
|
|
2247
|
-
|
|
2685
|
+
const nextExtra = {
|
|
2248
2686
|
...harnessExtra,
|
|
2249
2687
|
sessionState,
|
|
2688
|
+
workers: nextWorkers,
|
|
2250
2689
|
lastTurnSummary: turnSummary,
|
|
2251
2690
|
lastValidatedAt: turnSummary.lastSuccessfulValidation?.timestamp ?? harnessExtra?.lastValidatedAt ?? null,
|
|
2252
2691
|
currentTurn: undefined,
|
|
2253
2692
|
harnessVersion: JUNCTION_HARNESS_VERSION,
|
|
2693
|
+
runLedger: transitionHarnessRunLedger(harnessExtra?.runLedger, {
|
|
2694
|
+
lifecycle: mapTurnOutcomeToRunLifecycle(resolvedOutcome),
|
|
2695
|
+
timestamp,
|
|
2696
|
+
idFactory: this.idFactory,
|
|
2697
|
+
owner: agent.internal ? "background" : "foreground",
|
|
2698
|
+
summary: turnSummary.uiSummary ?? turnSummary.nextRequiredAction,
|
|
2699
|
+
turnStartSeq: turnSummary.turnStartSeq,
|
|
2700
|
+
turnEndSeq: turnSummary.turnEndSeq,
|
|
2701
|
+
correlationId: agent.persistence?.sessionId ?? agent.runtimeInfo?.sessionId ?? null,
|
|
2702
|
+
modelId: agent.runtimeInfo?.model ?? agent.config.model ?? null,
|
|
2703
|
+
}).ledger,
|
|
2704
|
+
resumeState: touchHarnessResumeState(harnessExtra?.resumeState, {
|
|
2705
|
+
status: "live",
|
|
2706
|
+
source: harnessExtra?.resumeState?.source ?? "snapshot",
|
|
2707
|
+
timestamp,
|
|
2708
|
+
recoveryHint: resolvedOutcome === "provider_error"
|
|
2709
|
+
? "The previous run failed before completion."
|
|
2710
|
+
: resolvedOutcome === "canceled"
|
|
2711
|
+
? "The previous run was interrupted."
|
|
2712
|
+
: null,
|
|
2713
|
+
}),
|
|
2254
2714
|
providerInstructionStrategy: harnessExtra?.providerInstructionStrategy ??
|
|
2255
2715
|
resolveProviderInstructionStrategy(agent.provider),
|
|
2716
|
+
};
|
|
2717
|
+
if (harnessExtra?.sessionState?.verificationState !== sessionState.verificationState) {
|
|
2718
|
+
this.recordHarnessEvent(agent, {
|
|
2719
|
+
kind: "verification_changed",
|
|
2720
|
+
summary: `Verification is now ${sessionState.verificationState}.`,
|
|
2721
|
+
verificationState: sessionState.verificationState,
|
|
2722
|
+
details: {
|
|
2723
|
+
nextRequiredAction: sessionState.nextRequiredAction,
|
|
2724
|
+
},
|
|
2725
|
+
});
|
|
2726
|
+
}
|
|
2727
|
+
const previousWorkerKeys = Object.keys(harnessExtra?.workers ?? {}).sort();
|
|
2728
|
+
const nextWorkerKeys = Object.keys(nextWorkers).sort();
|
|
2729
|
+
if (!isDeepStrictEqual(previousWorkerKeys, nextWorkerKeys)) {
|
|
2730
|
+
for (const workerId of nextWorkerKeys.filter((workerId) => !previousWorkerKeys.includes(workerId))) {
|
|
2731
|
+
this.recordHarnessEvent(agent, {
|
|
2732
|
+
kind: "worker_updated",
|
|
2733
|
+
summary: `Tracked worker ${nextWorkers[workerId]?.purpose ?? workerId}.`,
|
|
2734
|
+
workerId,
|
|
2735
|
+
details: {
|
|
2736
|
+
status: nextWorkers[workerId]?.status ?? null,
|
|
2737
|
+
},
|
|
2738
|
+
});
|
|
2739
|
+
}
|
|
2740
|
+
}
|
|
2741
|
+
if (this.checkpointStorage && turnSummary.changedFiles.length > 0) {
|
|
2742
|
+
const checkpointId = this.idFactory();
|
|
2743
|
+
const task = this.captureCheckpoint(agent, {
|
|
2744
|
+
id: checkpointId,
|
|
2745
|
+
kind: "turn_final",
|
|
2746
|
+
objective: currentTurn.objective,
|
|
2747
|
+
summary: turnSummary.uiSummary,
|
|
2748
|
+
changedFiles: turnSummary.changedFiles,
|
|
2749
|
+
turnStartSeq: turnSummary.turnStartSeq,
|
|
2750
|
+
turnEndSeq: turnSummary.turnEndSeq,
|
|
2751
|
+
harnessSnapshot: nextExtra,
|
|
2752
|
+
}).then((record) => {
|
|
2753
|
+
if (!record) {
|
|
2754
|
+
return;
|
|
2755
|
+
}
|
|
2756
|
+
if (!this.isLiveAgent(agent)) {
|
|
2757
|
+
return;
|
|
2758
|
+
}
|
|
2759
|
+
this.publishCapturedCheckpoint(agent, this.checkpointStorage.toSummary(record), {
|
|
2760
|
+
eventSummary: "Created a restorable checkpoint for this completed turn.",
|
|
2761
|
+
details: {
|
|
2762
|
+
checkpointKind: "turn_final",
|
|
2763
|
+
turnEndSeq: turnSummary.turnEndSeq,
|
|
2764
|
+
changedFiles: turnSummary.changedFiles,
|
|
2765
|
+
},
|
|
2766
|
+
});
|
|
2767
|
+
});
|
|
2768
|
+
this.trackBackgroundTask(task);
|
|
2769
|
+
}
|
|
2770
|
+
setHarnessSessionExtra(agent.config, nextExtra);
|
|
2771
|
+
}
|
|
2772
|
+
async listAgentCheckpoints(agentId) {
|
|
2773
|
+
if (!this.checkpointStorage) {
|
|
2774
|
+
return [];
|
|
2775
|
+
}
|
|
2776
|
+
return (await this.checkpointStorage.list(agentId)).map((record) => this.checkpointStorage.toSummary(record));
|
|
2777
|
+
}
|
|
2778
|
+
async restoreAgentCheckpoint(agentId, checkpointId) {
|
|
2779
|
+
if (!this.checkpointStorage) {
|
|
2780
|
+
throw new Error("Checkpoint storage is not configured.");
|
|
2781
|
+
}
|
|
2782
|
+
const agent = this.requireAgent(agentId);
|
|
2783
|
+
if (agent.lifecycle === "running" || agent.pendingRun) {
|
|
2784
|
+
await this.cancelAgentRun(agentId);
|
|
2785
|
+
}
|
|
2786
|
+
const record = await this.checkpointStorage.get(agentId, checkpointId);
|
|
2787
|
+
if (!record) {
|
|
2788
|
+
throw new Error(`Checkpoint not found: ${checkpointId}`);
|
|
2789
|
+
}
|
|
2790
|
+
await this.checkpointStorage.restore(record);
|
|
2791
|
+
agent.config.systemPrompt = record.harness.baseSystemPrompt?.trim() || undefined;
|
|
2792
|
+
setHarnessSessionExtra(agent.config, {
|
|
2793
|
+
...record.harness,
|
|
2794
|
+
currentTurn: undefined,
|
|
2795
|
+
checkpoints: this.upsertCheckpointSummary(record.harness.checkpoints, {
|
|
2796
|
+
id: record.id,
|
|
2797
|
+
kind: record.kind,
|
|
2798
|
+
createdAt: record.createdAt,
|
|
2799
|
+
objective: record.objective,
|
|
2800
|
+
summary: record.summary,
|
|
2801
|
+
changedFiles: record.changedFiles,
|
|
2802
|
+
restorable: record.restorable,
|
|
2803
|
+
turnStartSeq: record.turnStartSeq,
|
|
2804
|
+
turnEndSeq: record.turnEndSeq,
|
|
2805
|
+
}),
|
|
2806
|
+
lastCheckpointId: record.id,
|
|
2807
|
+
harnessVersion: JUNCTION_HARNESS_VERSION,
|
|
2808
|
+
resumeState: touchHarnessResumeState(record.harness.resumeState, {
|
|
2809
|
+
status: "checkpoint_restored",
|
|
2810
|
+
source: "checkpoint",
|
|
2811
|
+
timestamp: new Date().toISOString(),
|
|
2812
|
+
recoveryHint: "Workspace restored from a saved checkpoint.",
|
|
2813
|
+
}),
|
|
2814
|
+
});
|
|
2815
|
+
this.recordHarnessEvent(agent, {
|
|
2816
|
+
kind: "checkpoint_restored",
|
|
2817
|
+
summary: "Restored the workspace to the selected checkpoint.",
|
|
2818
|
+
checkpointId: record.id,
|
|
2819
|
+
details: {
|
|
2820
|
+
changedFiles: record.changedFiles,
|
|
2821
|
+
turnEndSeq: record.turnEndSeq,
|
|
2822
|
+
},
|
|
2256
2823
|
});
|
|
2824
|
+
this.touchUpdatedAt(agent);
|
|
2825
|
+
this.emitState(agent);
|
|
2826
|
+
await this.persistSnapshot(agent);
|
|
2827
|
+
await this.refreshRuntimeInfo(agent);
|
|
2828
|
+
return this.agents.get(agentId) ?? agent;
|
|
2257
2829
|
}
|
|
2258
2830
|
requireClient(provider) {
|
|
2259
2831
|
const client = this.clients.get(provider);
|