@adhdev/daemon-core 0.9.77-rc.49 → 0.9.77-rc.50
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/commands/router.d.ts +1 -0
- package/dist/index.js +161 -12
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +161 -12
- package/dist/index.mjs.map +1 -1
- package/dist/mesh/mesh-events.d.ts +8 -0
- package/dist/mesh/mesh-ledger.d.ts +38 -0
- package/package.json +1 -1
- package/src/commands/router.ts +54 -2
- package/src/mesh/mesh-events.ts +90 -4
- package/src/mesh/mesh-ledger.ts +88 -1
package/dist/index.mjs
CHANGED
|
@@ -793,9 +793,11 @@ __export(mesh_ledger_exports, {
|
|
|
793
793
|
MAX_LEDGER_SLICE_LIMIT: () => MAX_LEDGER_SLICE_LIMIT,
|
|
794
794
|
appendLedgerEntry: () => appendLedgerEntry,
|
|
795
795
|
appendRemoteLedgerEntries: () => appendRemoteLedgerEntries,
|
|
796
|
+
buildTaskCompletionEvidence: () => buildTaskCompletionEvidence,
|
|
796
797
|
getLedgerDir: () => getLedgerDir,
|
|
797
798
|
getLedgerSummary: () => getLedgerSummary,
|
|
798
799
|
getSessionRecoveryContext: () => getSessionRecoveryContext,
|
|
800
|
+
isIntentionalCleanupStopEntry: () => isIntentionalCleanupStopEntry,
|
|
799
801
|
meshLedgerEvents: () => meshLedgerEvents,
|
|
800
802
|
readLedgerEntries: () => readLedgerEntries,
|
|
801
803
|
readLedgerSlice: () => readLedgerSlice
|
|
@@ -804,6 +806,11 @@ import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as rea
|
|
|
804
806
|
import { join as join5 } from "path";
|
|
805
807
|
import { randomUUID as randomUUID4 } from "crypto";
|
|
806
808
|
import { EventEmitter } from "events";
|
|
809
|
+
function isIntentionalCleanupStopEntry(entry) {
|
|
810
|
+
if (entry.kind !== "session_stopped" && entry.kind !== "task_failed" && entry.kind !== "task_stalled") return false;
|
|
811
|
+
const payload = entry.payload && typeof entry.payload === "object" && !Array.isArray(entry.payload) ? entry.payload : {};
|
|
812
|
+
return payload.intentional === true && (payload.reason === "operator_cleanup" || payload.intentionalStopReason === "operator_cleanup" || payload.source === "mesh_cleanup_sessions" || payload.source === "mesh_remove_node");
|
|
813
|
+
}
|
|
807
814
|
function getLedgerDir() {
|
|
808
815
|
const dir = join5(getConfigDir(), LEDGER_DIR_NAME);
|
|
809
816
|
if (!existsSync5(dir)) {
|
|
@@ -819,6 +826,37 @@ function getRotatedPath(meshId, index) {
|
|
|
819
826
|
const safe = meshId.replace(/[^a-zA-Z0-9_-]/g, "_");
|
|
820
827
|
return join5(getLedgerDir(), `${safe}.${index}.jsonl`);
|
|
821
828
|
}
|
|
829
|
+
function buildTaskCompletionEvidence(opts) {
|
|
830
|
+
const providerSessionId = opts.providerSessionId?.trim() || void 0;
|
|
831
|
+
const providerType = opts.providerType?.trim() || void 0;
|
|
832
|
+
return {
|
|
833
|
+
source: "agent_status_event",
|
|
834
|
+
event: opts.event,
|
|
835
|
+
nodeId: opts.nodeId,
|
|
836
|
+
sessionId: opts.sessionId,
|
|
837
|
+
providerType,
|
|
838
|
+
completedAt: opts.completedAt || (/* @__PURE__ */ new Date()).toISOString(),
|
|
839
|
+
transcriptHandle: {
|
|
840
|
+
kind: providerSessionId ? "provider_session" : "runtime_session",
|
|
841
|
+
sessionId: opts.sessionId,
|
|
842
|
+
providerSessionId,
|
|
843
|
+
finalSummaryAvailable: typeof opts.finalSummary === "string" && opts.finalSummary.trim().length > 0
|
|
844
|
+
},
|
|
845
|
+
git: {
|
|
846
|
+
status: "deferred",
|
|
847
|
+
reason: "ordinary_completion_git_status_not_checked"
|
|
848
|
+
},
|
|
849
|
+
validation: {
|
|
850
|
+
status: "deferred",
|
|
851
|
+
commandsRun: [],
|
|
852
|
+
reason: "ordinary_completion_validation_not_run"
|
|
853
|
+
},
|
|
854
|
+
checkpoint: {
|
|
855
|
+
attempted: false,
|
|
856
|
+
reason: "not_attempted_for_ordinary_completion"
|
|
857
|
+
}
|
|
858
|
+
};
|
|
859
|
+
}
|
|
822
860
|
function appendLedgerEntry(meshId, partial) {
|
|
823
861
|
const entry = {
|
|
824
862
|
id: randomUUID4(),
|
|
@@ -979,15 +1017,17 @@ function getLedgerSummary(meshId) {
|
|
|
979
1017
|
summary.taskCompleted++;
|
|
980
1018
|
break;
|
|
981
1019
|
case "task_failed": {
|
|
1020
|
+
if (isIntentionalCleanupStopEntry(entry)) break;
|
|
982
1021
|
summary.taskFailed++;
|
|
983
1022
|
if (new Date(entry.timestamp).getTime() >= recentFailureCutoff) {
|
|
984
1023
|
summary.recentFailures++;
|
|
985
1024
|
}
|
|
986
1025
|
break;
|
|
987
1026
|
}
|
|
988
|
-
case "task_stalled":
|
|
989
|
-
summary.taskStalled++;
|
|
1027
|
+
case "task_stalled": {
|
|
1028
|
+
if (!isIntentionalCleanupStopEntry(entry)) summary.taskStalled++;
|
|
990
1029
|
break;
|
|
1030
|
+
}
|
|
991
1031
|
case "session_launched":
|
|
992
1032
|
summary.sessionLaunched++;
|
|
993
1033
|
break;
|
|
@@ -1026,6 +1066,7 @@ function getSessionRecoveryContext(meshId, opts) {
|
|
|
1026
1066
|
if (new Date(e.timestamp).getTime() < recentWindow) break;
|
|
1027
1067
|
if (opts.nodeId && e.nodeId !== opts.nodeId) continue;
|
|
1028
1068
|
if (e.kind === "task_failed") {
|
|
1069
|
+
if (isIntentionalCleanupStopEntry(e)) continue;
|
|
1029
1070
|
consecutiveNodeFailures++;
|
|
1030
1071
|
} else if (e.kind === "task_completed" || e.kind === "task_dispatched") {
|
|
1031
1072
|
break;
|
|
@@ -1663,6 +1704,28 @@ function getMeshWithCache(components, meshId) {
|
|
|
1663
1704
|
if (localMesh) return localMesh;
|
|
1664
1705
|
return components.router?.getCachedInlineMesh(meshId);
|
|
1665
1706
|
}
|
|
1707
|
+
function isIntentionalCleanupStopMetadata(event) {
|
|
1708
|
+
return event.intentional === true || event.intentionalStop === true || event.operatorCleanup === true || event.reason === "operator_cleanup" || event.stopReason === "operator_cleanup" || event.cleanupReason === "operator_cleanup" || event.source === "mesh_cleanup_sessions" || event.source === "mesh_remove_node";
|
|
1709
|
+
}
|
|
1710
|
+
function hasRecentIntentionalCleanupStop(meshId, sessionId, nodeId) {
|
|
1711
|
+
if (!sessionId && !nodeId) return false;
|
|
1712
|
+
const cutoff = Date.now() - INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS;
|
|
1713
|
+
const entries = readLedgerEntries(meshId);
|
|
1714
|
+
for (let i = entries.length - 1; i >= 0; i--) {
|
|
1715
|
+
const entry = entries[i];
|
|
1716
|
+
const timestamp = new Date(entry.timestamp).getTime();
|
|
1717
|
+
if (!Number.isNaN(timestamp) && timestamp < cutoff) break;
|
|
1718
|
+
if (!isIntentionalCleanupStopEntry(entry)) continue;
|
|
1719
|
+
if (sessionId && entry.sessionId === sessionId) return true;
|
|
1720
|
+
if (!sessionId && nodeId && entry.nodeId === nodeId) return true;
|
|
1721
|
+
}
|
|
1722
|
+
return false;
|
|
1723
|
+
}
|
|
1724
|
+
function shouldSuppressIntentionalCleanupStop(args) {
|
|
1725
|
+
if (args.event !== "agent:stopped" && args.event !== "monitor:long_generating") return false;
|
|
1726
|
+
if (isIntentionalCleanupStopMetadata(args.metadataEvent)) return true;
|
|
1727
|
+
return hasRecentIntentionalCleanupStop(args.meshId, args.sessionId, args.nodeId);
|
|
1728
|
+
}
|
|
1666
1729
|
function tryAssignQueueTask(components, meshId, nodeId, sessionId, providerType) {
|
|
1667
1730
|
const task = claimNextTask(meshId, nodeId, sessionId);
|
|
1668
1731
|
if (!task) {
|
|
@@ -1999,6 +2062,22 @@ Do NOT retry on this node. Consider reassigning to a different node or asking th
|
|
|
1999
2062
|
return "";
|
|
2000
2063
|
}
|
|
2001
2064
|
function injectMeshSystemMessage(components, args) {
|
|
2065
|
+
const eventSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
2066
|
+
const eventNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId);
|
|
2067
|
+
const intentionalCleanupStop = shouldSuppressIntentionalCleanupStop({
|
|
2068
|
+
event: args.event,
|
|
2069
|
+
meshId: args.meshId,
|
|
2070
|
+
metadataEvent: args.metadataEvent,
|
|
2071
|
+
sessionId: eventSessionId || void 0,
|
|
2072
|
+
nodeId: eventNodeId || void 0
|
|
2073
|
+
});
|
|
2074
|
+
if (intentionalCleanupStop) {
|
|
2075
|
+
if (eventSessionId && eventNodeId) {
|
|
2076
|
+
remoteIdleSessions.delete(`${eventNodeId}:${eventSessionId}`);
|
|
2077
|
+
}
|
|
2078
|
+
LOG.info("MeshEvents", `Suppressed ${args.event} for intentionally cleanup-stopped session ${eventSessionId || "(unknown session)"}`);
|
|
2079
|
+
return { success: true, forwarded: 0, suppressed: true, intentionalCleanupStop: true };
|
|
2080
|
+
}
|
|
2002
2081
|
let completedTaskForLedger = null;
|
|
2003
2082
|
if (args.event === "agent:generating_completed") {
|
|
2004
2083
|
const sessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId);
|
|
@@ -2032,7 +2111,15 @@ function injectMeshSystemMessage(components, args) {
|
|
|
2032
2111
|
taskId: completedTask.id,
|
|
2033
2112
|
completedViaReady: true,
|
|
2034
2113
|
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2035
|
-
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2114
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
2115
|
+
evidence: buildTaskCompletionEvidence({
|
|
2116
|
+
event: "agent:ready",
|
|
2117
|
+
nodeId,
|
|
2118
|
+
sessionId,
|
|
2119
|
+
providerType: providerType || void 0,
|
|
2120
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2121
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2122
|
+
})
|
|
2036
2123
|
}
|
|
2037
2124
|
});
|
|
2038
2125
|
} catch (e) {
|
|
@@ -2067,17 +2154,29 @@ function injectMeshSystemMessage(components, args) {
|
|
|
2067
2154
|
const ledgerKind = EVENT_TO_LEDGER_KIND[args.event];
|
|
2068
2155
|
if (ledgerKind) {
|
|
2069
2156
|
try {
|
|
2157
|
+
const ledgerNodeId = readNonEmptyString(args.nodeId) || readNonEmptyString(args.metadataEvent.meshNodeId) || void 0;
|
|
2158
|
+
const ledgerSessionId = resolveEventSessionId(args.metadataEvent, args.sourceInstanceId) || void 0;
|
|
2159
|
+
const ledgerProviderType = readNonEmptyString(args.metadataEvent.providerType) || void 0;
|
|
2160
|
+
const completionEvidence = ledgerKind === "task_completed" && ledgerNodeId && ledgerSessionId ? buildTaskCompletionEvidence({
|
|
2161
|
+
event: "agent:generating_completed",
|
|
2162
|
+
nodeId: ledgerNodeId,
|
|
2163
|
+
sessionId: ledgerSessionId,
|
|
2164
|
+
providerType: ledgerProviderType,
|
|
2165
|
+
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2166
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2167
|
+
}) : void 0;
|
|
2070
2168
|
appendLedgerEntry(args.meshId, {
|
|
2071
2169
|
kind: ledgerKind,
|
|
2072
|
-
nodeId:
|
|
2073
|
-
sessionId:
|
|
2074
|
-
providerType:
|
|
2170
|
+
nodeId: ledgerNodeId,
|
|
2171
|
+
sessionId: ledgerSessionId,
|
|
2172
|
+
providerType: ledgerProviderType,
|
|
2075
2173
|
payload: {
|
|
2076
2174
|
event: args.event,
|
|
2077
2175
|
nodeLabel: args.nodeLabel,
|
|
2078
2176
|
taskId: completedTaskForLedger?.id || void 0,
|
|
2079
2177
|
providerSessionId: readNonEmptyString(args.metadataEvent.providerSessionId) || void 0,
|
|
2080
|
-
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0
|
|
2178
|
+
finalSummary: readNonEmptyString(args.metadataEvent.finalSummary) || void 0,
|
|
2179
|
+
evidence: completionEvidence
|
|
2081
2180
|
}
|
|
2082
2181
|
});
|
|
2083
2182
|
} catch (e) {
|
|
@@ -2193,7 +2292,14 @@ function handleMeshForwardEvent(components, payload) {
|
|
|
2193
2292
|
targetSessionId: readNonEmptyString(payload.targetSessionId) || readNonEmptyString(payload.sessionId) || readNonEmptyString(payload.instanceId),
|
|
2194
2293
|
providerType: readNonEmptyString(payload.providerType),
|
|
2195
2294
|
providerSessionId: readNonEmptyString(payload.providerSessionId),
|
|
2196
|
-
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary)
|
|
2295
|
+
finalSummary: readNonEmptyString(payload.finalSummary) || readNonEmptyString(payload.summary),
|
|
2296
|
+
intentional: payload.intentional === true,
|
|
2297
|
+
intentionalStop: payload.intentionalStop === true,
|
|
2298
|
+
operatorCleanup: payload.operatorCleanup === true,
|
|
2299
|
+
reason: readNonEmptyString(payload.reason),
|
|
2300
|
+
stopReason: readNonEmptyString(payload.stopReason),
|
|
2301
|
+
cleanupReason: readNonEmptyString(payload.cleanupReason),
|
|
2302
|
+
source: readNonEmptyString(payload.source)
|
|
2197
2303
|
}
|
|
2198
2304
|
});
|
|
2199
2305
|
}
|
|
@@ -2229,7 +2335,7 @@ function setupMeshEventForwarding(components) {
|
|
|
2229
2335
|
});
|
|
2230
2336
|
});
|
|
2231
2337
|
}
|
|
2232
|
-
var remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
|
|
2338
|
+
var remoteIdleSessions, MAX_PENDING_EVENTS, pendingMeshCoordinatorEvents, MESH_COORDINATOR_EVENTS, EVENT_TO_LEDGER_KIND, INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS, autoLaunchInProgress, autoLaunchCooldownUntil, AUTO_LAUNCH_COOLDOWN_MS;
|
|
2233
2339
|
var init_mesh_events = __esm({
|
|
2234
2340
|
"src/mesh/mesh-events.ts"() {
|
|
2235
2341
|
"use strict";
|
|
@@ -2256,6 +2362,7 @@ var init_mesh_events = __esm({
|
|
|
2256
2362
|
"agent:stopped": "task_failed",
|
|
2257
2363
|
"monitor:long_generating": "task_stalled"
|
|
2258
2364
|
};
|
|
2365
|
+
INTENTIONAL_CLEANUP_STOP_SUPPRESSION_MS = 30 * 60 * 1e3;
|
|
2259
2366
|
autoLaunchInProgress = /* @__PURE__ */ new Set();
|
|
2260
2367
|
autoLaunchCooldownUntil = /* @__PURE__ */ new Map();
|
|
2261
2368
|
AUTO_LAUNCH_COOLDOWN_MS = 5e3;
|
|
@@ -23982,6 +24089,27 @@ var DaemonCommandRouter = class {
|
|
|
23982
24089
|
isCompletedHostedSession(record) {
|
|
23983
24090
|
return record?.lifecycle === "stopped" || record?.lifecycle === "failed" || record?.lifecycle === "interrupted";
|
|
23984
24091
|
}
|
|
24092
|
+
async recordIntentionalMeshSessionStop(args) {
|
|
24093
|
+
try {
|
|
24094
|
+
const { appendLedgerEntry: appendLedgerEntry2 } = await Promise.resolve().then(() => (init_mesh_ledger(), mesh_ledger_exports));
|
|
24095
|
+
appendLedgerEntry2(args.meshId, {
|
|
24096
|
+
kind: "session_stopped",
|
|
24097
|
+
nodeId: args.nodeId,
|
|
24098
|
+
sessionId: args.sessionId,
|
|
24099
|
+
payload: {
|
|
24100
|
+
intentional: true,
|
|
24101
|
+
reason: "operator_cleanup",
|
|
24102
|
+
intentionalStopReason: "operator_cleanup",
|
|
24103
|
+
source: args.source,
|
|
24104
|
+
cleanupMode: args.mode,
|
|
24105
|
+
action: args.action,
|
|
24106
|
+
workspace: typeof args.node?.workspace === "string" ? args.node.workspace : void 0
|
|
24107
|
+
}
|
|
24108
|
+
});
|
|
24109
|
+
} catch (e) {
|
|
24110
|
+
LOG.warn("MeshCleanup", `Failed to record intentional cleanup stop for ${args.sessionId}: ${e?.message || e}`);
|
|
24111
|
+
}
|
|
24112
|
+
}
|
|
23985
24113
|
async cleanupMeshSessions(args) {
|
|
23986
24114
|
if (args.mode === "preserve") {
|
|
23987
24115
|
return { success: true, mode: "preserve", matchedCount: 0, stoppedSessionIds: [], deletedSessionIds: [], skippedSessionIds: [] };
|
|
@@ -23998,6 +24126,21 @@ var DaemonCommandRouter = class {
|
|
|
23998
24126
|
const deleteUnsupportedSessionIds = [];
|
|
23999
24127
|
const recordsRemainSessionIds = [];
|
|
24000
24128
|
const errors = [];
|
|
24129
|
+
const cleanupSource = args.source || "mesh_cleanup_sessions";
|
|
24130
|
+
const markedIntentionalStopSessionIds = /* @__PURE__ */ new Set();
|
|
24131
|
+
const markIntentionalStop = async (sessionId, action) => {
|
|
24132
|
+
if (args.dryRun || markedIntentionalStopSessionIds.has(sessionId)) return;
|
|
24133
|
+
markedIntentionalStopSessionIds.add(sessionId);
|
|
24134
|
+
await this.recordIntentionalMeshSessionStop({
|
|
24135
|
+
meshId: args.meshId,
|
|
24136
|
+
nodeId: args.nodeId,
|
|
24137
|
+
node: args.node,
|
|
24138
|
+
sessionId,
|
|
24139
|
+
mode: args.mode,
|
|
24140
|
+
source: cleanupSource,
|
|
24141
|
+
action
|
|
24142
|
+
});
|
|
24143
|
+
};
|
|
24001
24144
|
const matchedBySurfaceKind = {
|
|
24002
24145
|
live_runtime: 0,
|
|
24003
24146
|
recovery_snapshot: 0,
|
|
@@ -24020,7 +24163,10 @@ var DaemonCommandRouter = class {
|
|
|
24020
24163
|
try {
|
|
24021
24164
|
if (args.mode === "stop") {
|
|
24022
24165
|
if (!completed) {
|
|
24023
|
-
if (!args.dryRun)
|
|
24166
|
+
if (!args.dryRun) {
|
|
24167
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
24168
|
+
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
24169
|
+
}
|
|
24024
24170
|
stoppedSessionIds.push(sessionId);
|
|
24025
24171
|
} else {
|
|
24026
24172
|
skippedSessionIds.push(sessionId);
|
|
@@ -24037,6 +24183,7 @@ var DaemonCommandRouter = class {
|
|
|
24037
24183
|
continue;
|
|
24038
24184
|
}
|
|
24039
24185
|
if (args.mode === "stop_and_delete") {
|
|
24186
|
+
if (!completed) await markIntentionalStop(sessionId, "delete_session_force");
|
|
24040
24187
|
if (!args.dryRun) await this.deps.sessionHostControl.deleteSession(sessionId, { force: true });
|
|
24041
24188
|
deletedSessionIds.push(sessionId);
|
|
24042
24189
|
continue;
|
|
@@ -24048,6 +24195,7 @@ var DaemonCommandRouter = class {
|
|
|
24048
24195
|
recordsRemainSessionIds.push(sessionId);
|
|
24049
24196
|
if (args.mode === "stop_and_delete" && !completed) {
|
|
24050
24197
|
try {
|
|
24198
|
+
await markIntentionalStop(sessionId, "stop_session");
|
|
24051
24199
|
await this.deps.sessionHostControl.stopSession(sessionId);
|
|
24052
24200
|
stoppedSessionIds.push(sessionId);
|
|
24053
24201
|
} catch (stopError) {
|
|
@@ -24948,7 +25096,8 @@ var DaemonCommandRouter = class {
|
|
|
24948
25096
|
node,
|
|
24949
25097
|
mode,
|
|
24950
25098
|
sessionIds,
|
|
24951
|
-
dryRun: args?.dryRun === true
|
|
25099
|
+
dryRun: args?.dryRun === true,
|
|
25100
|
+
source: "mesh_cleanup_sessions"
|
|
24952
25101
|
});
|
|
24953
25102
|
return result;
|
|
24954
25103
|
} catch (e) {
|
|
@@ -25083,7 +25232,7 @@ var DaemonCommandRouter = class {
|
|
|
25083
25232
|
);
|
|
25084
25233
|
let sessionCleanup;
|
|
25085
25234
|
if (node && sessionCleanupMode !== "preserve") {
|
|
25086
|
-
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode });
|
|
25235
|
+
sessionCleanup = await this.cleanupMeshSessions({ meshId, nodeId, node, mode: sessionCleanupMode, source: "mesh_remove_node" });
|
|
25087
25236
|
if (sessionCleanup.success === false) return { success: false, removed: false, sessionCleanup };
|
|
25088
25237
|
}
|
|
25089
25238
|
let worktreeCleanup;
|