syntaur 0.67.0 → 0.69.0
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/.claude-plugin/plugin.json +1 -1
- package/dashboard/dist/assets/{_basePickBy-CE2CIvur.js → _basePickBy-DKk6tHtk.js} +1 -1
- package/dashboard/dist/assets/{_baseUniq-BznLQqID.js → _baseUniq-DM-f7DWz.js} +1 -1
- package/dashboard/dist/assets/{arc-B6JqtWve.js → arc-ZBlA3YdV.js} +1 -1
- package/dashboard/dist/assets/{architectureDiagram-2XIMDMQ5-P8UCT3rj.js → architectureDiagram-2XIMDMQ5-BUmvtGTF.js} +1 -1
- package/dashboard/dist/assets/{blockDiagram-WCTKOSBZ-C1ATZKSf.js → blockDiagram-WCTKOSBZ-B3qxWK6s.js} +1 -1
- package/dashboard/dist/assets/{c4Diagram-IC4MRINW-AvN1yayQ.js → c4Diagram-IC4MRINW-BEq_UJO-.js} +1 -1
- package/dashboard/dist/assets/channel-fypxffzQ.js +1 -0
- package/dashboard/dist/assets/{chunk-4BX2VUAB-CyYz6mlJ.js → chunk-4BX2VUAB-C-Y9ryMm.js} +1 -1
- package/dashboard/dist/assets/{chunk-55IACEB6-QyOF7ox_.js → chunk-55IACEB6-CGdtbsjw.js} +1 -1
- package/dashboard/dist/assets/{chunk-FMBD7UC4-DVTHM99U.js → chunk-FMBD7UC4-DllxJhUp.js} +1 -1
- package/dashboard/dist/assets/{chunk-JSJVCQXG-DQfxaQtT.js → chunk-JSJVCQXG-jjMM8O5F.js} +1 -1
- package/dashboard/dist/assets/{chunk-KX2RTZJC-4R1oobH6.js → chunk-KX2RTZJC-B_6BPltQ.js} +1 -1
- package/dashboard/dist/assets/{chunk-NQ4KR5QH-D8H_7yNS.js → chunk-NQ4KR5QH-D0hJiXHp.js} +1 -1
- package/dashboard/dist/assets/{chunk-QZHKN3VN-DLxDUSuo.js → chunk-QZHKN3VN-BCWo4hLS.js} +1 -1
- package/dashboard/dist/assets/{chunk-WL4C6EOR-CWuFDkVp.js → chunk-WL4C6EOR-DH_jEAwg.js} +1 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-1KnjQvtL.js +1 -0
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-1KnjQvtL.js +1 -0
- package/dashboard/dist/assets/clone-CKMabBhS.js +1 -0
- package/dashboard/dist/assets/{cose-bilkent-S5V4N54A-D23Dy_Za.js → cose-bilkent-S5V4N54A-5ld00TOH.js} +1 -1
- package/dashboard/dist/assets/{dagre-KLK3FWXG-CaKBk8eh.js → dagre-KLK3FWXG-Cnu6eQWy.js} +1 -1
- package/dashboard/dist/assets/{diagram-E7M64L7V-BAPQPki-.js → diagram-E7M64L7V-_CBBKNP-.js} +1 -1
- package/dashboard/dist/assets/{diagram-IFDJBPK2-Cla6qyvn.js → diagram-IFDJBPK2-DE6WjIb1.js} +1 -1
- package/dashboard/dist/assets/{diagram-P4PSJMXO-DnTZq_y6.js → diagram-P4PSJMXO-DpW7UzNK.js} +1 -1
- package/dashboard/dist/assets/{erDiagram-INFDFZHY-yc1_7ebn.js → erDiagram-INFDFZHY-BD2409fE.js} +1 -1
- package/dashboard/dist/assets/{flowDiagram-PKNHOUZH-CMt2tF6O.js → flowDiagram-PKNHOUZH-1p0khhFI.js} +1 -1
- package/dashboard/dist/assets/{ganttDiagram-A5KZAMGK-CXiK-5Gp.js → ganttDiagram-A5KZAMGK-B2zFyA4s.js} +1 -1
- package/dashboard/dist/assets/{gitGraphDiagram-K3NZZRJ6-B4PbW06T.js → gitGraphDiagram-K3NZZRJ6-bH-4YH7h.js} +1 -1
- package/dashboard/dist/assets/{graph-CjdFy-q-.js → graph-BT24B6iQ.js} +1 -1
- package/dashboard/dist/assets/{index-DlUgV5eO.css → index-BZwPAi8K.css} +1 -1
- package/dashboard/dist/assets/index-Cxqr3rQB.js +670 -0
- package/dashboard/dist/assets/{infoDiagram-LFFYTUFH-BeOWVt7p.js → infoDiagram-LFFYTUFH-CMzP4Hcg.js} +1 -1
- package/dashboard/dist/assets/{ishikawaDiagram-PHBUUO56-LE7swZOH.js → ishikawaDiagram-PHBUUO56-DMEmFC7M.js} +1 -1
- package/dashboard/dist/assets/{journeyDiagram-4ABVD52K-DG9-sksf.js → journeyDiagram-4ABVD52K-CAtzYQUm.js} +1 -1
- package/dashboard/dist/assets/{kanban-definition-K7BYSVSG-7CKAw6eI.js → kanban-definition-K7BYSVSG-d1JVbtvX.js} +1 -1
- package/dashboard/dist/assets/{layout-CHChKPuz.js → layout-BVuI38I_.js} +1 -1
- package/dashboard/dist/assets/{linear-BWSj4au4.js → linear-Bc8PGMbp.js} +1 -1
- package/dashboard/dist/assets/{mermaid.core-CAwQaPqG.js → mermaid.core-UtwFYLNj.js} +4 -4
- package/dashboard/dist/assets/{mindmap-definition-YRQLILUH-CtZWMkVN.js → mindmap-definition-YRQLILUH-DHc5RCDj.js} +1 -1
- package/dashboard/dist/assets/{pieDiagram-SKSYHLDU-CstCoaK7.js → pieDiagram-SKSYHLDU-9anIsdIA.js} +1 -1
- package/dashboard/dist/assets/{quadrantDiagram-337W2JSQ-DD8zRtaB.js → quadrantDiagram-337W2JSQ-FZ0D9HnU.js} +1 -1
- package/dashboard/dist/assets/{requirementDiagram-Z7DCOOCP-BWpKIK3b.js → requirementDiagram-Z7DCOOCP-BkjCvH_u.js} +1 -1
- package/dashboard/dist/assets/{sankeyDiagram-WA2Y5GQK-08gqQtFM.js → sankeyDiagram-WA2Y5GQK-DzqwYHDo.js} +1 -1
- package/dashboard/dist/assets/{sequenceDiagram-2WXFIKYE-DsfIDBUH.js → sequenceDiagram-2WXFIKYE-BW4g5Ao-.js} +1 -1
- package/dashboard/dist/assets/{stateDiagram-RAJIS63D-Dqzfk_yy.js → stateDiagram-RAJIS63D-D0tKU3Z0.js} +1 -1
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-DxWQhjNO.js +1 -0
- package/dashboard/dist/assets/{timeline-definition-YZTLITO2-DgH_p9jR.js → timeline-definition-YZTLITO2-Bu269QDX.js} +1 -1
- package/dashboard/dist/assets/{treemap-KZPCXAKY-7XKqMhq9.js → treemap-KZPCXAKY-BGu_rrva.js} +1 -1
- package/dashboard/dist/assets/{vennDiagram-LZ73GAT5-BFtNLBWM.js → vennDiagram-LZ73GAT5-Cx_n5FRZ.js} +1 -1
- package/dashboard/dist/assets/{xychartDiagram-JWTSCODW-CQPevMNl.js → xychartDiagram-JWTSCODW-BOJsKV_W.js} +1 -1
- package/dashboard/dist/index.html +2 -2
- package/dist/dashboard/server.js +459 -63
- package/dist/dashboard/server.js.map +1 -1
- package/dist/index.js +1158 -702
- package/dist/index.js.map +1 -1
- package/dist/launch/index.d.ts +18 -0
- package/dist/launch/index.js +120 -12
- package/dist/launch/index.js.map +1 -1
- package/package.json +1 -1
- package/platforms/claude-code/.claude-plugin/plugin.json +1 -1
- package/platforms/claude-code/hooks/hooks.json +1 -1
- package/platforms/claude-code/hooks/session-cleanup.sh +21 -0
- package/platforms/codex/.codex-plugin/plugin.json +1 -1
- package/platforms/codex/hooks.json +1 -1
- package/platforms/codex/scripts/session-cleanup.sh +13 -0
- package/platforms/hermes/plugins/syntaur/__pycache__/__init__.cpython-312.pyc +0 -0
- package/platforms/hermes/plugins/syntaur/__pycache__/boundary.cpython-312.pyc +0 -0
- package/dashboard/dist/assets/channel-IZujZyS6.js +0 -1
- package/dashboard/dist/assets/classDiagram-VBA2DB6C-ChnJofe3.js +0 -1
- package/dashboard/dist/assets/classDiagram-v2-RAHNMMFH-ChnJofe3.js +0 -1
- package/dashboard/dist/assets/clone-JjIbzsqJ.js +0 -1
- package/dashboard/dist/assets/index-COOcebN7.js +0 -659
- package/dashboard/dist/assets/stateDiagram-v2-FVOUBMTO-D542UPiR.js +0 -1
package/dist/dashboard/server.js
CHANGED
|
@@ -937,6 +937,38 @@ CREATE TABLE IF NOT EXISTS meta (key TEXT PRIMARY KEY, value TEXT);
|
|
|
937
937
|
});
|
|
938
938
|
|
|
939
939
|
// src/lifecycle/event-emit.ts
|
|
940
|
+
var event_emit_exports = {};
|
|
941
|
+
__export(event_emit_exports, {
|
|
942
|
+
emitEvent: () => emitEvent,
|
|
943
|
+
isSuppressingEvents: () => isSuppressingEvents,
|
|
944
|
+
recordStatusEvent: () => recordStatusEvent,
|
|
945
|
+
resolveActor: () => resolveActor,
|
|
946
|
+
setSuppressEvents: () => setSuppressEvents,
|
|
947
|
+
withSuppressedEvents: () => withSuppressedEvents
|
|
948
|
+
});
|
|
949
|
+
function setSuppressEvents(value) {
|
|
950
|
+
suppressEvents = value;
|
|
951
|
+
}
|
|
952
|
+
function isSuppressingEvents() {
|
|
953
|
+
return suppressEvents;
|
|
954
|
+
}
|
|
955
|
+
function withSuppressedEvents(fn) {
|
|
956
|
+
const prior = suppressEvents;
|
|
957
|
+
suppressEvents = true;
|
|
958
|
+
try {
|
|
959
|
+
const result = fn();
|
|
960
|
+
if (result instanceof Promise) {
|
|
961
|
+
return result.finally(() => {
|
|
962
|
+
suppressEvents = prior;
|
|
963
|
+
});
|
|
964
|
+
}
|
|
965
|
+
suppressEvents = prior;
|
|
966
|
+
return result;
|
|
967
|
+
} catch (e) {
|
|
968
|
+
suppressEvents = prior;
|
|
969
|
+
throw e;
|
|
970
|
+
}
|
|
971
|
+
}
|
|
940
972
|
function resolveActor(by) {
|
|
941
973
|
return by ?? "system";
|
|
942
974
|
}
|
|
@@ -952,6 +984,10 @@ function recordStatusEvent(input) {
|
|
|
952
984
|
details: { from: input.from, to: input.to, command: input.command }
|
|
953
985
|
});
|
|
954
986
|
}
|
|
987
|
+
function emitEvent(input) {
|
|
988
|
+
if (suppressEvents) return;
|
|
989
|
+
recordEvent(input);
|
|
990
|
+
}
|
|
955
991
|
var suppressEvents;
|
|
956
992
|
var init_event_emit = __esm({
|
|
957
993
|
"src/lifecycle/event-emit.ts"() {
|
|
@@ -3764,7 +3800,9 @@ __export(config_exports, {
|
|
|
3764
3800
|
getTerminal: () => getTerminal,
|
|
3765
3801
|
normalizeFactDeclarations: () => normalizeFactDeclarations,
|
|
3766
3802
|
parseAgentCommand: () => parseAgentCommand,
|
|
3803
|
+
parseDurationMs: () => parseDurationMs,
|
|
3767
3804
|
parseSearchConfig: () => parseSearchConfig,
|
|
3805
|
+
parseStalenessConfig: () => parseStalenessConfig,
|
|
3768
3806
|
parseStatusConfig: () => parseStatusConfig,
|
|
3769
3807
|
parseTerminalConfig: () => parseTerminalConfig,
|
|
3770
3808
|
readConfig: () => readConfig,
|
|
@@ -3780,6 +3818,7 @@ __export(config_exports, {
|
|
|
3780
3818
|
validateDeriveConfig: () => validateDeriveConfig,
|
|
3781
3819
|
validateDeriveShape: () => validateDeriveShape,
|
|
3782
3820
|
validateFactDeclarations: () => validateFactDeclarations,
|
|
3821
|
+
validateStalenessConfig: () => validateStalenessConfig,
|
|
3783
3822
|
writeAgentsConfig: () => writeAgentsConfig,
|
|
3784
3823
|
writeHotkeyBindingsConfig: () => writeHotkeyBindingsConfig,
|
|
3785
3824
|
writeSearchConfig: () => writeSearchConfig,
|
|
@@ -3791,6 +3830,13 @@ __export(config_exports, {
|
|
|
3791
3830
|
import { readFile as readFile5 } from "fs/promises";
|
|
3792
3831
|
import { spawnSync } from "child_process";
|
|
3793
3832
|
import { resolve as resolve7, isAbsolute } from "path";
|
|
3833
|
+
function parseDurationMs(raw2) {
|
|
3834
|
+
const m = raw2.trim().match(DURATION_RE);
|
|
3835
|
+
if (!m) return null;
|
|
3836
|
+
const n = Number(m[1]);
|
|
3837
|
+
if (!Number.isFinite(n) || n <= 0) return null;
|
|
3838
|
+
return n * DURATION_UNIT_MS[m[2] ?? "ms"];
|
|
3839
|
+
}
|
|
3794
3840
|
function parseAgentCommand(value, agentId) {
|
|
3795
3841
|
if (typeof value !== "string" || value.trim() === "") {
|
|
3796
3842
|
throw new AgentConfigError(
|
|
@@ -5027,6 +5073,65 @@ ${cleanedFm}
|
|
|
5027
5073
|
---${afterFrontmatter}`;
|
|
5028
5074
|
await writeFileForce(configPath, newContent);
|
|
5029
5075
|
}
|
|
5076
|
+
function parseStalenessConfig(content) {
|
|
5077
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
5078
|
+
if (!match) return null;
|
|
5079
|
+
const fmBlock = match[1];
|
|
5080
|
+
const blockStart = fmBlock.match(/^staleness:\s*$/m);
|
|
5081
|
+
if (!blockStart) return null;
|
|
5082
|
+
const startIdx = (blockStart.index ?? 0) + blockStart[0].length;
|
|
5083
|
+
const lines = fmBlock.slice(startIdx).split("\n");
|
|
5084
|
+
const out = {};
|
|
5085
|
+
for (const line of lines) {
|
|
5086
|
+
if (line.trim() === "") continue;
|
|
5087
|
+
const trimmed = line.trimStart();
|
|
5088
|
+
const indent = line.length - trimmed.length;
|
|
5089
|
+
if (indent === 0) break;
|
|
5090
|
+
const ci = trimmed.indexOf(":");
|
|
5091
|
+
if (ci <= 0) continue;
|
|
5092
|
+
const key = trimmed.slice(0, ci).trim();
|
|
5093
|
+
const field = STALENESS_KEY_TO_FIELD[key];
|
|
5094
|
+
if (!field) continue;
|
|
5095
|
+
let value = trimmed.slice(ci + 1).trim();
|
|
5096
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
5097
|
+
value = value.slice(1, -1);
|
|
5098
|
+
}
|
|
5099
|
+
const ms = parseDurationMs(value);
|
|
5100
|
+
if (ms !== null) out[field] = ms;
|
|
5101
|
+
}
|
|
5102
|
+
return Object.keys(out).length > 0 ? out : null;
|
|
5103
|
+
}
|
|
5104
|
+
function validateStalenessConfig(content) {
|
|
5105
|
+
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
5106
|
+
if (!match) return [];
|
|
5107
|
+
const fmBlock = match[1];
|
|
5108
|
+
const blockStart = fmBlock.match(/^staleness:\s*$/m);
|
|
5109
|
+
if (!blockStart) return [];
|
|
5110
|
+
const startIdx = (blockStart.index ?? 0) + blockStart[0].length;
|
|
5111
|
+
const lines = fmBlock.slice(startIdx).split("\n");
|
|
5112
|
+
const problems = [];
|
|
5113
|
+
for (const line of lines) {
|
|
5114
|
+
if (line.trim() === "") continue;
|
|
5115
|
+
const trimmed = line.trimStart();
|
|
5116
|
+
const indent = line.length - trimmed.length;
|
|
5117
|
+
if (indent === 0) break;
|
|
5118
|
+
const ci = trimmed.indexOf(":");
|
|
5119
|
+
if (ci <= 0) continue;
|
|
5120
|
+
const key = trimmed.slice(0, ci).trim();
|
|
5121
|
+
let value = trimmed.slice(ci + 1).trim();
|
|
5122
|
+
if (value.startsWith('"') && value.endsWith('"') || value.startsWith("'") && value.endsWith("'")) {
|
|
5123
|
+
value = value.slice(1, -1);
|
|
5124
|
+
}
|
|
5125
|
+
if (!(key in STALENESS_KEY_TO_FIELD)) {
|
|
5126
|
+
problems.push(`staleness.${key}: unknown key (expected one of ${Object.keys(STALENESS_KEY_TO_FIELD).join(", ")})`);
|
|
5127
|
+
continue;
|
|
5128
|
+
}
|
|
5129
|
+
if (parseDurationMs(value) === null) {
|
|
5130
|
+
problems.push(`staleness.${key}: "${value}" is not a positive duration (e.g. 7d, 12h, 30m, 90s, 500ms)`);
|
|
5131
|
+
}
|
|
5132
|
+
}
|
|
5133
|
+
return problems;
|
|
5134
|
+
}
|
|
5030
5135
|
function parseSearchConfig(content) {
|
|
5031
5136
|
const match = content.match(/^---\n([\s\S]*?)\n---/);
|
|
5032
5137
|
if (!match) return null;
|
|
@@ -5309,7 +5414,9 @@ async function readConfig() {
|
|
|
5309
5414
|
}
|
|
5310
5415
|
})(),
|
|
5311
5416
|
searchConfig: parseSearchConfig(content),
|
|
5312
|
-
workspaceVisibility: parseWorkspaceVisibilityConfig(fmBlock)
|
|
5417
|
+
workspaceVisibility: parseWorkspaceVisibilityConfig(fmBlock),
|
|
5418
|
+
staleness: parseStalenessConfig(content),
|
|
5419
|
+
stalenessWatchdog: String(fm["stalenessWatchdog"]).toLowerCase() === "true"
|
|
5313
5420
|
};
|
|
5314
5421
|
}
|
|
5315
5422
|
function getAssignmentTypes(config) {
|
|
@@ -5372,7 +5479,7 @@ async function updateAgentsConfig(mutation, options = {}) {
|
|
|
5372
5479
|
await writeAgentsConfig(next);
|
|
5373
5480
|
return { previous, next, written: true };
|
|
5374
5481
|
}
|
|
5375
|
-
var DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, SESSION_AUTO_TRACK_VALUES, AgentConfigError, DEFAULT_STATUS_COLORS, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
|
|
5482
|
+
var STALENESS_KEY_TO_FIELD, DURATION_RE, DURATION_UNIT_MS, DEFAULT_ASSIGNMENT_TYPES, DEFAULT_CONFIG, AUTO_CREATE_WORKTREE_VALUES, SESSION_AUTO_TRACK_VALUES, AgentConfigError, DEFAULT_STATUS_COLORS, KNOWN_AGENT_SCALAR_FIELDS, migratedConfigPaths, TerminalConfigError;
|
|
5376
5483
|
var init_config2 = __esm({
|
|
5377
5484
|
"src/utils/config.ts"() {
|
|
5378
5485
|
"use strict";
|
|
@@ -5389,6 +5496,21 @@ var init_config2 = __esm({
|
|
|
5389
5496
|
init_terminal_schema();
|
|
5390
5497
|
init_search_schema();
|
|
5391
5498
|
init_workspace_visibility_schema();
|
|
5499
|
+
STALENESS_KEY_TO_FIELD = {
|
|
5500
|
+
inProgressNoActivity: "inProgressNoActivityMs",
|
|
5501
|
+
readyUnclaimed: "readyUnclaimedMs",
|
|
5502
|
+
reviewAging: "reviewAgingMs",
|
|
5503
|
+
blockedAging: "blockedAgingMs",
|
|
5504
|
+
planApprovalAging: "planApprovalAgingMs"
|
|
5505
|
+
};
|
|
5506
|
+
DURATION_RE = /^(\d+(?:\.\d+)?)\s*(ms|s|m|h|d)?$/;
|
|
5507
|
+
DURATION_UNIT_MS = {
|
|
5508
|
+
ms: 1,
|
|
5509
|
+
s: 1e3,
|
|
5510
|
+
m: 6e4,
|
|
5511
|
+
h: 36e5,
|
|
5512
|
+
d: 864e5
|
|
5513
|
+
};
|
|
5392
5514
|
DEFAULT_ASSIGNMENT_TYPES = {
|
|
5393
5515
|
definitions: [
|
|
5394
5516
|
{ id: "feature", label: "Feature" },
|
|
@@ -5431,7 +5553,9 @@ var init_config2 = __esm({
|
|
|
5431
5553
|
searchConfig: null,
|
|
5432
5554
|
workspaceVisibility: {
|
|
5433
5555
|
hidden: []
|
|
5434
|
-
}
|
|
5556
|
+
},
|
|
5557
|
+
staleness: null,
|
|
5558
|
+
stalenessWatchdog: false
|
|
5435
5559
|
};
|
|
5436
5560
|
AUTO_CREATE_WORKTREE_VALUES = ["skip", "ask", "always"];
|
|
5437
5561
|
SESSION_AUTO_TRACK_VALUES = ["all", "workspaces-only", "off"];
|
|
@@ -7793,6 +7917,74 @@ var init_overviewCopy = __esm({
|
|
|
7793
7917
|
}
|
|
7794
7918
|
});
|
|
7795
7919
|
|
|
7920
|
+
// src/staleness/classify.ts
|
|
7921
|
+
function resolveStaleThresholds(overrides) {
|
|
7922
|
+
const merged = { ...DEFAULT_STALE_THRESHOLDS };
|
|
7923
|
+
if (overrides) {
|
|
7924
|
+
for (const key of Object.keys(merged)) {
|
|
7925
|
+
const v = overrides[key];
|
|
7926
|
+
if (typeof v === "number" && Number.isFinite(v) && v > 0) merged[key] = v;
|
|
7927
|
+
}
|
|
7928
|
+
}
|
|
7929
|
+
return merged;
|
|
7930
|
+
}
|
|
7931
|
+
function classifyNeedsAttention(input, thresholds = DEFAULT_STALE_THRESHOLDS) {
|
|
7932
|
+
if (input.isTerminal) return [];
|
|
7933
|
+
const reasons = [];
|
|
7934
|
+
const age = input.statusAgeMs;
|
|
7935
|
+
const aged = (gate) => age !== null && age >= gate;
|
|
7936
|
+
const blocked = input.disposition === "blocked" || input.blockedReason !== null;
|
|
7937
|
+
if (input.phase === IN_PROGRESS_PHASE && !blocked && aged(thresholds.inProgressNoActivityMs) && input.lastActivityMs !== null && input.lastActivityMs >= thresholds.inProgressNoActivityMs) {
|
|
7938
|
+
reasons.push({
|
|
7939
|
+
kind: "in_progress_no_activity",
|
|
7940
|
+
label: "In progress, but no recent activity",
|
|
7941
|
+
severity: "medium"
|
|
7942
|
+
});
|
|
7943
|
+
}
|
|
7944
|
+
if (input.phase === READY_PHASE && input.assignee === null && aged(thresholds.readyUnclaimedMs)) {
|
|
7945
|
+
reasons.push({
|
|
7946
|
+
kind: "ready_unclaimed",
|
|
7947
|
+
label: "Ready to implement, unclaimed",
|
|
7948
|
+
severity: "medium"
|
|
7949
|
+
});
|
|
7950
|
+
}
|
|
7951
|
+
if (input.phase === REVIEW_PHASE && aged(thresholds.reviewAgingMs)) {
|
|
7952
|
+
reasons.push({ kind: "review_aging", label: "Awaiting review", severity: "high" });
|
|
7953
|
+
}
|
|
7954
|
+
if (blocked && aged(thresholds.blockedAgingMs)) {
|
|
7955
|
+
reasons.push({ kind: "blocked_aging", label: "Blocked and aging", severity: "high" });
|
|
7956
|
+
}
|
|
7957
|
+
if (input.phase === PLANNING_PHASE && input.planExists && !input.planApproved && aged(thresholds.planApprovalAgingMs)) {
|
|
7958
|
+
reasons.push({
|
|
7959
|
+
kind: "plan_awaiting_approval",
|
|
7960
|
+
label: "Plan awaiting approval",
|
|
7961
|
+
severity: "medium"
|
|
7962
|
+
});
|
|
7963
|
+
}
|
|
7964
|
+
if (input.depsSatisfied === false && (input.phase === READY_PHASE || input.phase === IN_PROGRESS_PHASE)) {
|
|
7965
|
+
reasons.push({ kind: "deps_unsatisfied", label: "Unmet dependencies", severity: "high" });
|
|
7966
|
+
}
|
|
7967
|
+
return reasons;
|
|
7968
|
+
}
|
|
7969
|
+
var DAY, DEFAULT_STALE_THRESHOLDS, PLANNING_PHASE, READY_PHASE, IN_PROGRESS_PHASE, REVIEW_PHASE;
|
|
7970
|
+
var init_classify = __esm({
|
|
7971
|
+
"src/staleness/classify.ts"() {
|
|
7972
|
+
"use strict";
|
|
7973
|
+
DAY = 24 * 60 * 60 * 1e3;
|
|
7974
|
+
DEFAULT_STALE_THRESHOLDS = {
|
|
7975
|
+
inProgressNoActivityMs: 7 * DAY,
|
|
7976
|
+
readyUnclaimedMs: 3 * DAY,
|
|
7977
|
+
reviewAgingMs: 3 * DAY,
|
|
7978
|
+
blockedAgingMs: 3 * DAY,
|
|
7979
|
+
planApprovalAgingMs: 3 * DAY
|
|
7980
|
+
};
|
|
7981
|
+
PLANNING_PHASE = "ready_for_planning";
|
|
7982
|
+
READY_PHASE = "ready_to_implement";
|
|
7983
|
+
IN_PROGRESS_PHASE = "in_progress";
|
|
7984
|
+
REVIEW_PHASE = "review";
|
|
7985
|
+
}
|
|
7986
|
+
});
|
|
7987
|
+
|
|
7796
7988
|
// src/dashboard/servers.ts
|
|
7797
7989
|
import { readdir as readdir9, readFile as readFile12, unlink as unlink2 } from "fs/promises";
|
|
7798
7990
|
import { resolve as resolve15 } from "path";
|
|
@@ -8427,7 +8619,38 @@ var init_scanner = __esm({
|
|
|
8427
8619
|
});
|
|
8428
8620
|
|
|
8429
8621
|
// src/dashboard/api.ts
|
|
8430
|
-
|
|
8622
|
+
var api_exports = {};
|
|
8623
|
+
__export(api_exports, {
|
|
8624
|
+
WorkspaceBlockedError: () => WorkspaceBlockedError,
|
|
8625
|
+
clearStatusConfigCache: () => clearStatusConfigCache,
|
|
8626
|
+
collectStaleCandidates: () => collectStaleCandidates,
|
|
8627
|
+
createWorkspace: () => createWorkspace,
|
|
8628
|
+
deleteWorkspace: () => deleteWorkspace,
|
|
8629
|
+
getAssignmentDetail: () => getAssignmentDetail,
|
|
8630
|
+
getAssignmentDetailById: () => getAssignmentDetailById,
|
|
8631
|
+
getEditableDocument: () => getEditableDocument,
|
|
8632
|
+
getEditableDocumentById: () => getEditableDocumentById,
|
|
8633
|
+
getHelp: () => getHelp,
|
|
8634
|
+
getMemoryDetail: () => getMemoryDetail,
|
|
8635
|
+
getOverview: () => getOverview,
|
|
8636
|
+
getPlaybookDetail: () => getPlaybookDetail,
|
|
8637
|
+
getProjectDetail: () => getProjectDetail,
|
|
8638
|
+
getResourceDetail: () => getResourceDetail,
|
|
8639
|
+
getStatusConfig: () => getStatusConfig,
|
|
8640
|
+
installRecordsInvalidation: () => installRecordsInvalidation,
|
|
8641
|
+
invalidateRecordsCache: () => invalidateRecordsCache,
|
|
8642
|
+
listAllMemories: () => listAllMemories,
|
|
8643
|
+
listAllResources: () => listAllResources,
|
|
8644
|
+
listArchived: () => listArchived,
|
|
8645
|
+
listAssignmentsBoard: () => listAssignmentsBoard,
|
|
8646
|
+
listPlaybooks: () => listPlaybooks,
|
|
8647
|
+
listProjects: () => listProjects,
|
|
8648
|
+
listWorkspaceRecords: () => listWorkspaceRecords,
|
|
8649
|
+
listWorkspaces: () => listWorkspaces,
|
|
8650
|
+
resolveProjectPath: () => resolveProjectPath,
|
|
8651
|
+
resolveWorkspaceMembers: () => resolveWorkspaceMembers
|
|
8652
|
+
});
|
|
8653
|
+
import { readdir as readdir10, readFile as readFile13, writeFile as writeFile3, stat as stat2 } from "fs/promises";
|
|
8431
8654
|
import { resolve as resolve17, dirname as dirname2, basename } from "path";
|
|
8432
8655
|
function clearFrontmatterField(content, key) {
|
|
8433
8656
|
const fieldRegex = new RegExp(`^(${escapeRegExp2(key)}:)\\s*.*$`, "m");
|
|
@@ -8818,10 +9041,10 @@ async function getOverview(projectsDir, serversDir2, assignmentsDir2, options =
|
|
|
8818
9041
|
(total, record) => total + (record.summary.progress["failed"] ?? 0),
|
|
8819
9042
|
0
|
|
8820
9043
|
),
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
9044
|
+
// Derived from the SAME classifier verdict as the stale segment (via the
|
|
9045
|
+
// pre-cap segment total) so the badge count can never diverge from the
|
|
9046
|
+
// listed rows.
|
|
9047
|
+
staleAssignments: segments.stale.total
|
|
8825
9048
|
},
|
|
8826
9049
|
hero,
|
|
8827
9050
|
segments,
|
|
@@ -9973,9 +10196,77 @@ function segmentSeverity(segment) {
|
|
|
9973
10196
|
return "medium";
|
|
9974
10197
|
}
|
|
9975
10198
|
}
|
|
10199
|
+
function topStaleReason(reasons) {
|
|
10200
|
+
if (reasons.length === 0) return null;
|
|
10201
|
+
return reasons.slice().sort((a, b) => STALE_SEVERITY_RANK[b.severity] - STALE_SEVERITY_RANK[a.severity])[0];
|
|
10202
|
+
}
|
|
10203
|
+
async function readProgressActivityMs(progressPath, now) {
|
|
10204
|
+
try {
|
|
10205
|
+
const s = await stat2(progressPath);
|
|
10206
|
+
return Math.max(0, now - s.mtimeMs);
|
|
10207
|
+
} catch {
|
|
10208
|
+
return null;
|
|
10209
|
+
}
|
|
10210
|
+
}
|
|
10211
|
+
function classifyAssignmentRecord(assignment, terminalStatuses, depsSatisfied, lastActivityMs, thresholds) {
|
|
10212
|
+
const virtuals = deriveStatusVirtuals(assignment, terminalStatuses);
|
|
10213
|
+
return classifyNeedsAttention(
|
|
10214
|
+
{
|
|
10215
|
+
phase: virtuals.phase,
|
|
10216
|
+
disposition: virtuals.disposition,
|
|
10217
|
+
isTerminal: terminalStatuses.has(assignment.status),
|
|
10218
|
+
assignee: assignment.assignee ?? null,
|
|
10219
|
+
blockedReason: assignment.blockedReason,
|
|
10220
|
+
depsSatisfied,
|
|
10221
|
+
// plan_awaiting_approval is deferred to the decision inbox's plan-approval
|
|
10222
|
+
// category for now; pass values that keep that reason dormant.
|
|
10223
|
+
planExists: false,
|
|
10224
|
+
planApproved: true,
|
|
10225
|
+
statusAgeMs: virtuals.statusAge,
|
|
10226
|
+
lastActivityMs
|
|
10227
|
+
},
|
|
10228
|
+
thresholds
|
|
10229
|
+
);
|
|
10230
|
+
}
|
|
10231
|
+
async function collectStaleCandidates(projectsDir, assignmentsDir2) {
|
|
10232
|
+
const [projectRecords, standaloneRecords] = await Promise.all([
|
|
10233
|
+
listProjectRecords(projectsDir),
|
|
10234
|
+
listStandaloneRecords(assignmentsDir2)
|
|
10235
|
+
]);
|
|
10236
|
+
const { terminalStatuses } = await getStatusConfig();
|
|
10237
|
+
const thresholds = resolveStaleThresholds((await readConfig()).staleness);
|
|
10238
|
+
const now = Date.now();
|
|
10239
|
+
const out = [];
|
|
10240
|
+
for (const record of projectRecords) {
|
|
10241
|
+
if (isProjectArchived(record.summary)) continue;
|
|
10242
|
+
const projectPath = resolve17(projectsDir, record.summary.slug);
|
|
10243
|
+
const depMap = /* @__PURE__ */ new Map();
|
|
10244
|
+
for (const a of record.assignments) depMap.set(a.slug, a.status);
|
|
10245
|
+
for (const assignment of activeAssignments(record.assignments)) {
|
|
10246
|
+
const depsSatisfied = assignment.dependsOn.length === 0 ? true : (await getUnmetDependencies(projectPath, assignment.dependsOn, terminalStatuses, depMap)).length === 0;
|
|
10247
|
+
const lastActivityMs = await readProgressActivityMs(
|
|
10248
|
+
resolve17(projectPath, "assignments", assignment.slug, "progress.md"),
|
|
10249
|
+
now
|
|
10250
|
+
);
|
|
10251
|
+
const reasons = classifyAssignmentRecord(assignment, terminalStatuses, depsSatisfied, lastActivityMs, thresholds);
|
|
10252
|
+
if (reasons.length > 0) {
|
|
10253
|
+
out.push({ assignmentId: assignment.id, projectSlug: record.summary.slug, reasons });
|
|
10254
|
+
}
|
|
10255
|
+
}
|
|
10256
|
+
}
|
|
10257
|
+
for (const sr of standaloneRecords) {
|
|
10258
|
+
if (sr.record.archived === true) continue;
|
|
10259
|
+
const lastActivityMs = await readProgressActivityMs(resolve17(sr.assignmentDir, "progress.md"), now);
|
|
10260
|
+
const reasons = classifyAssignmentRecord(sr.record, terminalStatuses, true, lastActivityMs, thresholds);
|
|
10261
|
+
if (reasons.length > 0) out.push({ assignmentId: sr.record.id, projectSlug: null, reasons });
|
|
10262
|
+
}
|
|
10263
|
+
return out;
|
|
10264
|
+
}
|
|
9976
10265
|
async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standaloneRecords, traces) {
|
|
9977
10266
|
const now = Date.now();
|
|
9978
10267
|
const buckets = emptyBuckets();
|
|
10268
|
+
const { terminalStatuses } = await getStatusConfig();
|
|
10269
|
+
const staleThresholds = resolveStaleThresholds((await readConfig()).staleness);
|
|
9979
10270
|
const newestPool = [];
|
|
9980
10271
|
for (const record of projectRecords) {
|
|
9981
10272
|
if (isProjectArchived(record.summary)) continue;
|
|
@@ -9984,6 +10275,7 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
9984
10275
|
depMap.set(a.slug, a.status);
|
|
9985
10276
|
}
|
|
9986
10277
|
const visibleAssignments = activeAssignments(record.assignments);
|
|
10278
|
+
const projectPath = resolve17(projectsDir, record.summary.slug);
|
|
9987
10279
|
const resolvedTransitions = await Promise.all(
|
|
9988
10280
|
visibleAssignments.map(async (assignment) => {
|
|
9989
10281
|
const t0 = traces ? performance.now() : 0;
|
|
@@ -9995,13 +10287,25 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
9995
10287
|
{ traces, dependencyStatusMap: depMap }
|
|
9996
10288
|
);
|
|
9997
10289
|
if (traces) accumulatePhase(traces, "get-available-transitions", performance.now() - t0);
|
|
9998
|
-
|
|
10290
|
+
const depsSatisfied = assignment.dependsOn.length === 0 ? true : (await getUnmetDependencies(projectPath, assignment.dependsOn, terminalStatuses, depMap)).length === 0;
|
|
10291
|
+
const lastActivityMs = await readProgressActivityMs(
|
|
10292
|
+
resolve17(projectPath, "assignments", assignment.slug, "progress.md"),
|
|
10293
|
+
now
|
|
10294
|
+
);
|
|
10295
|
+
return { assignment, availableTransitions, depsSatisfied, lastActivityMs };
|
|
9999
10296
|
})
|
|
10000
10297
|
);
|
|
10001
|
-
for (const { assignment, availableTransitions } of resolvedTransitions) {
|
|
10298
|
+
for (const { assignment, availableTransitions, depsSatisfied, lastActivityMs } of resolvedTransitions) {
|
|
10002
10299
|
const segmentId = STATUS_TO_SEGMENT[assignment.status];
|
|
10003
|
-
const
|
|
10004
|
-
const
|
|
10300
|
+
const isTerminal = terminalStatuses.has(assignment.status);
|
|
10301
|
+
const staleReasons = classifyAssignmentRecord(
|
|
10302
|
+
assignment,
|
|
10303
|
+
terminalStatuses,
|
|
10304
|
+
depsSatisfied,
|
|
10305
|
+
lastActivityMs,
|
|
10306
|
+
staleThresholds
|
|
10307
|
+
);
|
|
10308
|
+
const stale = staleReasons.length > 0;
|
|
10005
10309
|
const agingMs = Math.max(0, now - parseTimestamp(assignment.updated));
|
|
10006
10310
|
const baseId = `${record.summary.slug}:${assignment.slug}`;
|
|
10007
10311
|
const shared = {
|
|
@@ -10030,11 +10334,12 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
10030
10334
|
buckets[segmentId].push(primary);
|
|
10031
10335
|
}
|
|
10032
10336
|
if (stale && !isTerminal) {
|
|
10337
|
+
const top = topStaleReason(staleReasons);
|
|
10033
10338
|
const staleItem = {
|
|
10034
10339
|
...shared,
|
|
10035
10340
|
id: `${baseId}:stale`,
|
|
10036
10341
|
severity: "low",
|
|
10037
|
-
reason: SEGMENT_REASON.stale,
|
|
10342
|
+
reason: top?.label ?? SEGMENT_REASON.stale,
|
|
10038
10343
|
segment: "stale"
|
|
10039
10344
|
};
|
|
10040
10345
|
buckets.stale.push(staleItem);
|
|
@@ -10058,14 +10363,22 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
10058
10363
|
const t0 = traces ? performance.now() : 0;
|
|
10059
10364
|
const availableTransitions = await getStandaloneAvailableTransitions(sr.record);
|
|
10060
10365
|
if (traces) accumulatePhase(traces, "get-available-transitions", performance.now() - t0);
|
|
10061
|
-
|
|
10366
|
+
const lastActivityMs = await readProgressActivityMs(resolve17(sr.assignmentDir, "progress.md"), now);
|
|
10367
|
+
return { sr, availableTransitions, lastActivityMs };
|
|
10062
10368
|
})
|
|
10063
10369
|
);
|
|
10064
|
-
for (const { sr, availableTransitions } of resolvedStandaloneTransitions) {
|
|
10370
|
+
for (const { sr, availableTransitions, lastActivityMs } of resolvedStandaloneTransitions) {
|
|
10065
10371
|
const assignment = sr.record;
|
|
10066
10372
|
const segmentId = STATUS_TO_SEGMENT[assignment.status];
|
|
10067
|
-
const
|
|
10068
|
-
const
|
|
10373
|
+
const isTerminal = terminalStatuses.has(assignment.status);
|
|
10374
|
+
const staleReasons = classifyAssignmentRecord(
|
|
10375
|
+
assignment,
|
|
10376
|
+
terminalStatuses,
|
|
10377
|
+
true,
|
|
10378
|
+
lastActivityMs,
|
|
10379
|
+
staleThresholds
|
|
10380
|
+
);
|
|
10381
|
+
const stale = staleReasons.length > 0;
|
|
10069
10382
|
const agingMs = Math.max(0, now - parseTimestamp(assignment.updated));
|
|
10070
10383
|
const baseId = `standalone:${sr.id}`;
|
|
10071
10384
|
const shared = {
|
|
@@ -10093,11 +10406,12 @@ async function buildOverviewSegmentBuckets(projectsDir, projectRecords, standalo
|
|
|
10093
10406
|
});
|
|
10094
10407
|
}
|
|
10095
10408
|
if (stale && !isTerminal) {
|
|
10409
|
+
const top = topStaleReason(staleReasons);
|
|
10096
10410
|
buckets.stale.push({
|
|
10097
10411
|
...shared,
|
|
10098
10412
|
id: `${baseId}:stale`,
|
|
10099
10413
|
severity: "low",
|
|
10100
|
-
reason: SEGMENT_REASON.stale,
|
|
10414
|
+
reason: top?.label ?? SEGMENT_REASON.stale,
|
|
10101
10415
|
segment: "stale"
|
|
10102
10416
|
});
|
|
10103
10417
|
}
|
|
@@ -10218,13 +10532,6 @@ function parseTimestamp(timestamp) {
|
|
|
10218
10532
|
const parsed = Date.parse(timestamp);
|
|
10219
10533
|
return Number.isFinite(parsed) ? parsed : 0;
|
|
10220
10534
|
}
|
|
10221
|
-
function isStale(updated) {
|
|
10222
|
-
const timestamp = parseTimestamp(updated);
|
|
10223
|
-
if (timestamp === 0) {
|
|
10224
|
-
return false;
|
|
10225
|
-
}
|
|
10226
|
-
return Date.now() - timestamp > STALE_ASSIGNMENT_MS;
|
|
10227
|
-
}
|
|
10228
10535
|
async function countOpenQuestions(projectPath, assignmentSlug) {
|
|
10229
10536
|
const commentsPath = resolve17(
|
|
10230
10537
|
projectPath,
|
|
@@ -10343,7 +10650,7 @@ async function getPlaybookDetail(playbooksDir2, slug) {
|
|
|
10343
10650
|
enabled
|
|
10344
10651
|
};
|
|
10345
10652
|
}
|
|
10346
|
-
var WorkspaceBlockedError,
|
|
10653
|
+
var WorkspaceBlockedError, RECENT_PROJECTS_LIMIT, RECENT_ACTIVITY_LIMIT, RECENT_SESSIONS_LIMIT, NEWEST_CREATED_LIMIT, SEGMENT_DISPLAY_CAP, STALE_LIMIT_DEFAULT, STALE_LIMIT_MAX, STATUS_TO_SEGMENT, HERO_PRIORITY, projectRecordsCache, standaloneRecordsCache, DEFAULT_TRANSITION_DEFINITIONS, _cachedConfig, REFERENCED_BY_LIMIT, migratedProjectsDirs, DEFAULT_GRAPH_COLORS, STALE_SEVERITY_RANK;
|
|
10347
10654
|
var init_api = __esm({
|
|
10348
10655
|
"src/dashboard/api.ts"() {
|
|
10349
10656
|
"use strict";
|
|
@@ -10361,6 +10668,7 @@ var init_api = __esm({
|
|
|
10361
10668
|
init_help();
|
|
10362
10669
|
init_agent_sessions();
|
|
10363
10670
|
init_overviewCopy();
|
|
10671
|
+
init_classify();
|
|
10364
10672
|
WorkspaceBlockedError = class extends Error {
|
|
10365
10673
|
blockedBy;
|
|
10366
10674
|
constructor(blockedBy) {
|
|
@@ -10371,7 +10679,6 @@ var init_api = __esm({
|
|
|
10371
10679
|
this.blockedBy = blockedBy;
|
|
10372
10680
|
}
|
|
10373
10681
|
};
|
|
10374
|
-
STALE_ASSIGNMENT_MS = 7 * 24 * 60 * 60 * 1e3;
|
|
10375
10682
|
RECENT_PROJECTS_LIMIT = 6;
|
|
10376
10683
|
RECENT_ACTIVITY_LIMIT = 12;
|
|
10377
10684
|
RECENT_SESSIONS_LIMIT = 10;
|
|
@@ -10379,7 +10686,6 @@ var init_api = __esm({
|
|
|
10379
10686
|
SEGMENT_DISPLAY_CAP = 5;
|
|
10380
10687
|
STALE_LIMIT_DEFAULT = 50;
|
|
10381
10688
|
STALE_LIMIT_MAX = 200;
|
|
10382
|
-
TERMINAL_STATUSES2 = /* @__PURE__ */ new Set(["completed", "failed", "archived"]);
|
|
10383
10689
|
STATUS_TO_SEGMENT = {
|
|
10384
10690
|
review: "readyForReview",
|
|
10385
10691
|
ready_to_implement: "readyToImplement",
|
|
@@ -10472,6 +10778,7 @@ var init_api = __esm({
|
|
|
10472
10778
|
failed: "fill:#9f2d2d,stroke:#651616,color:#ffffff",
|
|
10473
10779
|
review: "fill:#c6911e,stroke:#7a5a10,color:#ffffff"
|
|
10474
10780
|
};
|
|
10781
|
+
STALE_SEVERITY_RANK = { high: 3, medium: 2, low: 1 };
|
|
10475
10782
|
}
|
|
10476
10783
|
});
|
|
10477
10784
|
|
|
@@ -10907,12 +11214,13 @@ __export(recompute_exports, {
|
|
|
10907
11214
|
markDeriveMigrated: () => markDeriveMigrated,
|
|
10908
11215
|
recomputeAll: () => recomputeAll,
|
|
10909
11216
|
recomputeAndWrite: () => recomputeAndWrite,
|
|
11217
|
+
recomputeAssignmentDir: () => recomputeAssignmentDir,
|
|
10910
11218
|
recomputeDependents: () => recomputeDependents,
|
|
10911
11219
|
resolveDeriveContext: () => resolveDeriveContext
|
|
10912
11220
|
});
|
|
10913
11221
|
import { createHash as createHash2 } from "crypto";
|
|
10914
|
-
import { open, readdir as readdir12, readFile as readFile17, unlink as unlink5, stat as
|
|
10915
|
-
import { dirname as dirname4, resolve as resolve22 } from "path";
|
|
11222
|
+
import { open, readdir as readdir12, readFile as readFile17, unlink as unlink5, stat as stat3 } from "fs/promises";
|
|
11223
|
+
import { basename as basename3, dirname as dirname4, resolve as resolve22 } from "path";
|
|
10916
11224
|
async function isDeriveMigrated() {
|
|
10917
11225
|
return fileExists(resolve22(syntaurRoot(), MIGRATION_MARKER));
|
|
10918
11226
|
}
|
|
@@ -10952,7 +11260,7 @@ async function acquireLock(assignmentDir) {
|
|
|
10952
11260
|
const code = err.code;
|
|
10953
11261
|
if (code !== "EEXIST") throw err;
|
|
10954
11262
|
try {
|
|
10955
|
-
const info = await
|
|
11263
|
+
const info = await stat3(lockPath);
|
|
10956
11264
|
if (Date.now() - info.mtimeMs > LOCK_STALE_MS) {
|
|
10957
11265
|
await unlink5(lockPath).catch(() => {
|
|
10958
11266
|
});
|
|
@@ -11144,6 +11452,18 @@ async function recomputeAll(projectsDir, standaloneDir, opts) {
|
|
|
11144
11452
|
}
|
|
11145
11453
|
return summary;
|
|
11146
11454
|
}
|
|
11455
|
+
async function recomputeAssignmentDir(assignmentDir, cause, by) {
|
|
11456
|
+
try {
|
|
11457
|
+
const assignmentPath = resolve22(assignmentDir, "assignment.md");
|
|
11458
|
+
if (!await fileExists(assignmentPath)) return null;
|
|
11459
|
+
const parent = dirname4(assignmentDir);
|
|
11460
|
+
const projectDir = basename3(parent) === "assignments" ? dirname4(parent) : null;
|
|
11461
|
+
const context = await resolveDeriveContext();
|
|
11462
|
+
return await recomputeAndWrite(assignmentPath, { cause, by, projectDir, context });
|
|
11463
|
+
} catch {
|
|
11464
|
+
return null;
|
|
11465
|
+
}
|
|
11466
|
+
}
|
|
11147
11467
|
var LOCK_FILE, LOCK_STALE_MS, LOCK_WAIT_MS, LOCK_MAX_WAITS, CAS_RETRIES, MIGRATION_MARKER;
|
|
11148
11468
|
var init_recompute = __esm({
|
|
11149
11469
|
"src/lifecycle/recompute.ts"() {
|
|
@@ -11251,14 +11571,14 @@ var init_process_info = __esm({
|
|
|
11251
11571
|
});
|
|
11252
11572
|
|
|
11253
11573
|
// src/usage/cwd-extractor.ts
|
|
11254
|
-
import { open as open3, readdir as readdir13, stat as
|
|
11574
|
+
import { open as open3, readdir as readdir13, stat as stat4 } from "fs/promises";
|
|
11255
11575
|
import { join as join4 } from "path";
|
|
11256
11576
|
import { homedir as homedir3 } from "os";
|
|
11257
11577
|
async function extractClaudeSessionMeta(jsonlPath) {
|
|
11258
11578
|
const cwd = await derivePathFromTranscript(jsonlPath);
|
|
11259
11579
|
if (!cwd) return null;
|
|
11260
|
-
const
|
|
11261
|
-
const sessionId =
|
|
11580
|
+
const basename7 = jsonlPath.split("/").pop() ?? "";
|
|
11581
|
+
const sessionId = basename7.replace(/\.jsonl$/, "");
|
|
11262
11582
|
if (!sessionId) return null;
|
|
11263
11583
|
const startTs = await readFirstTimestamp(jsonlPath);
|
|
11264
11584
|
const endTs = await readLastTimestamp(jsonlPath);
|
|
@@ -11353,8 +11673,8 @@ async function* walkClaudeProjects(opts = {}) {
|
|
|
11353
11673
|
async function* walkCodexSessions(opts = {}) {
|
|
11354
11674
|
const root = resolveCodexSessionsRoot(opts.root);
|
|
11355
11675
|
for await (const filePath of walkJsonlRecursive(root)) {
|
|
11356
|
-
const
|
|
11357
|
-
if (!
|
|
11676
|
+
const basename7 = filePath.split("/").pop() ?? "";
|
|
11677
|
+
if (!basename7.endsWith(".jsonl")) continue;
|
|
11358
11678
|
if (opts.sinceMtimeMs !== void 0) {
|
|
11359
11679
|
const mtime = await mtimeMs(filePath);
|
|
11360
11680
|
if (mtime !== null && mtime < opts.sinceMtimeMs) continue;
|
|
@@ -11372,10 +11692,10 @@ function resolveCodexSessionsRoot(override) {
|
|
|
11372
11692
|
return join4(homedir3(), ".codex", "sessions");
|
|
11373
11693
|
}
|
|
11374
11694
|
async function extractPiSessionMeta(jsonlPath) {
|
|
11375
|
-
const
|
|
11376
|
-
const underscoreIdx =
|
|
11695
|
+
const basename7 = jsonlPath.split("/").pop() ?? "";
|
|
11696
|
+
const underscoreIdx = basename7.lastIndexOf("_");
|
|
11377
11697
|
if (underscoreIdx === -1) return null;
|
|
11378
|
-
const sessionId =
|
|
11698
|
+
const sessionId = basename7.slice(underscoreIdx + 1).replace(/\.jsonl$/, "");
|
|
11379
11699
|
if (!sessionId) return null;
|
|
11380
11700
|
let handle;
|
|
11381
11701
|
try {
|
|
@@ -11490,7 +11810,7 @@ async function* walkJsonlRecursive(root) {
|
|
|
11490
11810
|
}
|
|
11491
11811
|
async function mtimeMs(path) {
|
|
11492
11812
|
try {
|
|
11493
|
-
const s = await
|
|
11813
|
+
const s = await stat4(path);
|
|
11494
11814
|
return s.mtimeMs;
|
|
11495
11815
|
} catch {
|
|
11496
11816
|
return null;
|
|
@@ -12096,6 +12416,40 @@ var init_scanner2 = __esm({
|
|
|
12096
12416
|
}
|
|
12097
12417
|
});
|
|
12098
12418
|
|
|
12419
|
+
// src/staleness/watchdog.ts
|
|
12420
|
+
var watchdog_exports = {};
|
|
12421
|
+
__export(watchdog_exports, {
|
|
12422
|
+
runStalenessWatchdogTick: () => runStalenessWatchdogTick
|
|
12423
|
+
});
|
|
12424
|
+
function runStalenessWatchdogTick(candidates, seen, emit) {
|
|
12425
|
+
const staleNow = /* @__PURE__ */ new Map();
|
|
12426
|
+
for (const c of candidates) {
|
|
12427
|
+
if (c.reasons.length > 0) staleNow.set(c.assignmentId, c);
|
|
12428
|
+
}
|
|
12429
|
+
let newlyStale = 0;
|
|
12430
|
+
for (const [id, c] of staleNow) {
|
|
12431
|
+
if (!seen.has(id)) {
|
|
12432
|
+
seen.add(id);
|
|
12433
|
+
newlyStale++;
|
|
12434
|
+
emit({ assignmentId: id, projectSlug: c.projectSlug, type: "staleness-detected", reasons: c.reasons });
|
|
12435
|
+
}
|
|
12436
|
+
}
|
|
12437
|
+
let cleared = 0;
|
|
12438
|
+
for (const id of [...seen]) {
|
|
12439
|
+
if (!staleNow.has(id)) {
|
|
12440
|
+
seen.delete(id);
|
|
12441
|
+
cleared++;
|
|
12442
|
+
emit({ assignmentId: id, projectSlug: null, type: "staleness-cleared", reasons: [] });
|
|
12443
|
+
}
|
|
12444
|
+
}
|
|
12445
|
+
return { scanned: candidates.length, stale: staleNow.size, newlyStale, cleared };
|
|
12446
|
+
}
|
|
12447
|
+
var init_watchdog = __esm({
|
|
12448
|
+
"src/staleness/watchdog.ts"() {
|
|
12449
|
+
"use strict";
|
|
12450
|
+
}
|
|
12451
|
+
});
|
|
12452
|
+
|
|
12099
12453
|
// src/dashboard/server.ts
|
|
12100
12454
|
init_paths();
|
|
12101
12455
|
init_api();
|
|
@@ -13264,7 +13618,7 @@ init_timestamp();
|
|
|
13264
13618
|
init_fs();
|
|
13265
13619
|
init_git_worktree();
|
|
13266
13620
|
import { Router as Router2 } from "express";
|
|
13267
|
-
import { resolve as resolve23, basename as
|
|
13621
|
+
import { resolve as resolve23, basename as basename4, isAbsolute as isAbsolute4 } from "path";
|
|
13268
13622
|
import { rm, readFile as readFile18, open as fsOpen, stat as fsStat, realpath as fsRealpath } from "fs/promises";
|
|
13269
13623
|
import { spawnSync as spawnSync3 } from "child_process";
|
|
13270
13624
|
|
|
@@ -14485,7 +14839,7 @@ function createWriteRouter(projectsDir, assignmentsDir2, todosDir2) {
|
|
|
14485
14839
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
14486
14840
|
return;
|
|
14487
14841
|
}
|
|
14488
|
-
const document = await getEditableDocument(projectsDir, "memory",
|
|
14842
|
+
const document = await getEditableDocument(projectsDir, "memory", basename4(projectDir), itemSlug);
|
|
14489
14843
|
if (!document) {
|
|
14490
14844
|
res.status(404).json({ error: "Memory not found" });
|
|
14491
14845
|
return;
|
|
@@ -14504,7 +14858,7 @@ function createWriteRouter(projectsDir, assignmentsDir2, todosDir2) {
|
|
|
14504
14858
|
res.status(404).json({ error: `Project "${slug}" not found` });
|
|
14505
14859
|
return;
|
|
14506
14860
|
}
|
|
14507
|
-
const document = await getEditableDocument(projectsDir, "resource",
|
|
14861
|
+
const document = await getEditableDocument(projectsDir, "resource", basename4(projectDir), itemSlug);
|
|
14508
14862
|
if (!document) {
|
|
14509
14863
|
res.status(404).json({ error: "Resource not found" });
|
|
14510
14864
|
return;
|
|
@@ -14589,7 +14943,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
14589
14943
|
let content = renderItemStub(kind, {
|
|
14590
14944
|
slug: requestedSlug,
|
|
14591
14945
|
name,
|
|
14592
|
-
projectSlug:
|
|
14946
|
+
projectSlug: basename4(projectDir),
|
|
14593
14947
|
timestamp
|
|
14594
14948
|
});
|
|
14595
14949
|
const customBody = typeof body.body === "string" ? body.body : "";
|
|
@@ -14606,13 +14960,13 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
14606
14960
|
} catch (err) {
|
|
14607
14961
|
if (err.code === "EEXIST") {
|
|
14608
14962
|
res.status(409).json({
|
|
14609
|
-
error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${
|
|
14963
|
+
error: `${kind === "memory" ? "Memory" : "Resource"} with slug "${requestedSlug}" already exists in project "${basename4(projectDir)}".`
|
|
14610
14964
|
});
|
|
14611
14965
|
return;
|
|
14612
14966
|
}
|
|
14613
14967
|
throw err;
|
|
14614
14968
|
}
|
|
14615
|
-
res.status(201).json({ slug: requestedSlug, projectSlug:
|
|
14969
|
+
res.status(201).json({ slug: requestedSlug, projectSlug: basename4(projectDir), content });
|
|
14616
14970
|
} catch (error) {
|
|
14617
14971
|
console.error(`Error creating ${kind}:`, error);
|
|
14618
14972
|
res.status(500).json({ error: `Failed to create ${kind}: ${error.message}` });
|
|
@@ -14647,7 +15001,7 @@ ${body.startsWith("\n") ? body.slice(1) : body}${body.endsWith("\n") ? "" : "\n"
|
|
|
14647
15001
|
${nextBody}${nextBody.endsWith("\n") ? "" : "\n"}`;
|
|
14648
15002
|
merged = setTopLevelField(merged, "updated", nowTimestamp());
|
|
14649
15003
|
await writeFileForce(filePath, merged);
|
|
14650
|
-
const detail = await getItemDetail(kind,
|
|
15004
|
+
const detail = await getItemDetail(kind, basename4(projectDir), itemSlug);
|
|
14651
15005
|
res.json({ [kind]: detail, content: merged });
|
|
14652
15006
|
} catch (error) {
|
|
14653
15007
|
console.error(`Error updating ${kind}:`, error);
|
|
@@ -17811,25 +18165,29 @@ async function resolveSessionPlan(input, terminal) {
|
|
|
17811
18165
|
}
|
|
17812
18166
|
let cwd = session.path;
|
|
17813
18167
|
let fallbackWarning = null;
|
|
17814
|
-
if (session.
|
|
17815
|
-
const detail = await getAssignmentDetail(
|
|
18168
|
+
if (!isExistingDir(session.path)) {
|
|
18169
|
+
const detail = session.projectSlug && session.assignmentSlug ? await getAssignmentDetail(
|
|
17816
18170
|
input.projectsDir,
|
|
17817
18171
|
session.projectSlug,
|
|
17818
18172
|
session.assignmentSlug
|
|
17819
|
-
)
|
|
18173
|
+
) : session.assignmentSlug ? await getAssignmentDetailById(
|
|
18174
|
+
input.projectsDir,
|
|
18175
|
+
input.assignmentsDir,
|
|
18176
|
+
session.assignmentSlug
|
|
18177
|
+
) : null;
|
|
17820
18178
|
if (detail) {
|
|
17821
18179
|
const picked = resolveWorkspaceCwd({
|
|
17822
18180
|
worktreePath: detail.workspace.worktreePath,
|
|
17823
18181
|
repository: detail.workspace.repository,
|
|
17824
18182
|
branch: detail.workspace.branch,
|
|
17825
|
-
assignmentSlug:
|
|
18183
|
+
assignmentSlug: detail.slug
|
|
17826
18184
|
});
|
|
17827
18185
|
if (picked.cwd !== null) {
|
|
17828
18186
|
cwd = picked.cwd;
|
|
17829
18187
|
fallbackWarning = picked.fallbackWarning;
|
|
17830
18188
|
} else {
|
|
17831
18189
|
fallbackWarning = formatFallbackCwdWarning({
|
|
17832
|
-
assignmentSlug:
|
|
18190
|
+
assignmentSlug: detail.slug,
|
|
17833
18191
|
workspaceDir: session.path,
|
|
17834
18192
|
worktreePath: detail.workspace.worktreePath,
|
|
17835
18193
|
branch: detail.workspace.branch
|
|
@@ -17865,7 +18223,7 @@ async function resolveSessionPlan(input, terminal) {
|
|
|
17865
18223
|
// src/launch/execute.ts
|
|
17866
18224
|
import { spawn as spawn3 } from "child_process";
|
|
17867
18225
|
import { homedir as homedir5 } from "os";
|
|
17868
|
-
import { basename as
|
|
18226
|
+
import { basename as basename5, join as join6, resolve as resolve26 } from "path";
|
|
17869
18227
|
init_fs();
|
|
17870
18228
|
init_config2();
|
|
17871
18229
|
init_session_id();
|
|
@@ -17885,7 +18243,7 @@ var TerminalNotFoundError = class extends Error {
|
|
|
17885
18243
|
var realSpawn = (command, args, options) => spawn3(command, args, options);
|
|
17886
18244
|
var WRAPPER_COMMANDS = /* @__PURE__ */ new Set(["osascript", "open", "sh"]);
|
|
17887
18245
|
function isWrapperCommand(command) {
|
|
17888
|
-
return WRAPPER_COMMANDS.has(
|
|
18246
|
+
return WRAPPER_COMMANDS.has(basename5(command));
|
|
17889
18247
|
}
|
|
17890
18248
|
var WRAPPER_EXIT_TIMEOUT_MS = 1500;
|
|
17891
18249
|
async function executeLaunchPlan(plan, spawnFn = realSpawn) {
|
|
@@ -19665,7 +20023,7 @@ async function readEvents(jobId) {
|
|
|
19665
20023
|
|
|
19666
20024
|
// src/schedules/attempt.ts
|
|
19667
20025
|
import { createHash as createHash3 } from "crypto";
|
|
19668
|
-
import { open as open4, readFile as readFile22, stat as
|
|
20026
|
+
import { open as open4, readFile as readFile22, stat as stat5, unlink as unlink6 } from "fs/promises";
|
|
19669
20027
|
import { resolve as resolve31 } from "path";
|
|
19670
20028
|
|
|
19671
20029
|
// src/schedules/triggers.ts
|
|
@@ -19862,7 +20220,7 @@ async function acquireJobLock(id) {
|
|
|
19862
20220
|
const code = err.code;
|
|
19863
20221
|
if (code !== "EEXIST") throw err;
|
|
19864
20222
|
try {
|
|
19865
|
-
const info = await
|
|
20223
|
+
const info = await stat5(lockPath);
|
|
19866
20224
|
if (Date.now() - info.mtimeMs > LOCK_STALE_MS2) {
|
|
19867
20225
|
await unlink6(lockPath).catch(() => {
|
|
19868
20226
|
});
|
|
@@ -21864,8 +22222,8 @@ init_api();
|
|
|
21864
22222
|
import { raw } from "express";
|
|
21865
22223
|
|
|
21866
22224
|
// src/todos/attachments.ts
|
|
21867
|
-
import { mkdir as mkdir4, readdir as readdir15, stat as
|
|
21868
|
-
import { resolve as resolve39, basename as
|
|
22225
|
+
import { mkdir as mkdir4, readdir as readdir15, stat as stat6, rename as rename5, rm as rm4, unlink as unlink7, writeFile as writeFile6, cp } from "fs/promises";
|
|
22226
|
+
import { resolve as resolve39, basename as basename6, dirname as dirname9, extname } from "path";
|
|
21869
22227
|
|
|
21870
22228
|
// src/utils/proof-artifact-id.ts
|
|
21871
22229
|
import { randomBytes as randomBytes2 } from "crypto";
|
|
@@ -21938,7 +22296,7 @@ function isSafeInlineMime(mime) {
|
|
|
21938
22296
|
return SAFE_INLINE_MIME.has(mime);
|
|
21939
22297
|
}
|
|
21940
22298
|
function sanitizeAttachmentName(name) {
|
|
21941
|
-
let n =
|
|
22299
|
+
let n = basename6(name || "").replace(/["'\\/]/g, "_");
|
|
21942
22300
|
n = Array.from(n, (ch) => {
|
|
21943
22301
|
const code = ch.charCodeAt(0);
|
|
21944
22302
|
return code < 32 || code === 127 ? "_" : ch;
|
|
@@ -21961,7 +22319,7 @@ function attachmentDirFor(todosDir2, scopeId, todoId) {
|
|
|
21961
22319
|
}
|
|
21962
22320
|
async function dirExists(p) {
|
|
21963
22321
|
try {
|
|
21964
|
-
return (await
|
|
22322
|
+
return (await stat6(p)).isDirectory();
|
|
21965
22323
|
} catch {
|
|
21966
22324
|
return false;
|
|
21967
22325
|
}
|
|
@@ -21996,7 +22354,7 @@ async function listAttachments(todosDir2, scopeId, todoId) {
|
|
|
21996
22354
|
if (!ATTACHMENT_ID_RE.test(id)) continue;
|
|
21997
22355
|
const filename = stored.slice(sep2 + 2);
|
|
21998
22356
|
try {
|
|
21999
|
-
const st = await
|
|
22357
|
+
const st = await stat6(resolve39(dir, stored));
|
|
22000
22358
|
if (!st.isFile()) continue;
|
|
22001
22359
|
out.push({ id, filename, mime: mimeForName(filename), size: st.size, createdAt: st.mtime.toISOString() });
|
|
22002
22360
|
} catch {
|
|
@@ -24162,7 +24520,7 @@ init_fs();
|
|
|
24162
24520
|
init_config2();
|
|
24163
24521
|
import { execFile as execFile2 } from "child_process";
|
|
24164
24522
|
import { promisify as promisify2 } from "util";
|
|
24165
|
-
import { cp as cp2, mkdtemp, rm as rm5, readFile as readFile29, writeFile as writeFile7, unlink as unlink8, stat as
|
|
24523
|
+
import { cp as cp2, mkdtemp, rm as rm5, readFile as readFile29, writeFile as writeFile7, unlink as unlink8, stat as stat7, open as open5, rename as rename8 } from "fs/promises";
|
|
24166
24524
|
import { resolve as resolve42, join as join9 } from "path";
|
|
24167
24525
|
import { tmpdir } from "os";
|
|
24168
24526
|
var exec2 = promisify2(execFile2);
|
|
@@ -24256,7 +24614,7 @@ async function cloneOrInit(repoUrl, destDir) {
|
|
|
24256
24614
|
}
|
|
24257
24615
|
async function copyRecursive(src, dest) {
|
|
24258
24616
|
if (!await fileExists(src)) return;
|
|
24259
|
-
const s = await
|
|
24617
|
+
const s = await stat7(src);
|
|
24260
24618
|
if (s.isDirectory()) {
|
|
24261
24619
|
await ensureDir(dest);
|
|
24262
24620
|
await cp2(src, dest, { recursive: true, force: true });
|
|
@@ -25911,6 +26269,8 @@ function createDashboardServer(options) {
|
|
|
25911
26269
|
});
|
|
25912
26270
|
}
|
|
25913
26271
|
let watcherHandle = null;
|
|
26272
|
+
const STALENESS_WATCHDOG_INTERVAL_MS = 5 * 60 * 1e3;
|
|
26273
|
+
let stalenessWatchdogTimer = null;
|
|
25914
26274
|
return {
|
|
25915
26275
|
async start() {
|
|
25916
26276
|
const { recomputeAndWrite: recomputeAndWrite2, recomputeAll: recomputeAll2, resolveDeriveContext: resolveDeriveContext2, isDeriveMigrated: isDeriveMigrated2 } = await Promise.resolve().then(() => (init_recompute(), recompute_exports));
|
|
@@ -26007,6 +26367,38 @@ function createDashboardServer(options) {
|
|
|
26007
26367
|
onAgentSessionsChanged: () => broadcast({ type: "agent-sessions-updated", timestamp: (/* @__PURE__ */ new Date()).toISOString() })
|
|
26008
26368
|
});
|
|
26009
26369
|
startUsageCollector();
|
|
26370
|
+
const startupConfig = await readConfig();
|
|
26371
|
+
if (startupConfig.stalenessWatchdog) {
|
|
26372
|
+
const { collectStaleCandidates: collectStaleCandidates2 } = await Promise.resolve().then(() => (init_api(), api_exports));
|
|
26373
|
+
const { runStalenessWatchdogTick: runStalenessWatchdogTick2 } = await Promise.resolve().then(() => (init_watchdog(), watchdog_exports));
|
|
26374
|
+
const { emitEvent: emitEvent2 } = await Promise.resolve().then(() => (init_event_emit(), event_emit_exports));
|
|
26375
|
+
const stalenessSeen = /* @__PURE__ */ new Set();
|
|
26376
|
+
const watchdogTick = async () => {
|
|
26377
|
+
if (!await migrationGate()) return;
|
|
26378
|
+
try {
|
|
26379
|
+
const candidates = await collectStaleCandidates2(projectsDir, assignmentsDir2);
|
|
26380
|
+
const summary = runStalenessWatchdogTick2(candidates, stalenessSeen, (e) => {
|
|
26381
|
+
emitEvent2({
|
|
26382
|
+
assignmentId: e.assignmentId,
|
|
26383
|
+
projectSlug: e.projectSlug,
|
|
26384
|
+
type: e.type,
|
|
26385
|
+
actor: "system",
|
|
26386
|
+
details: { reasons: e.reasons.map((r) => r.kind) }
|
|
26387
|
+
});
|
|
26388
|
+
});
|
|
26389
|
+
if (summary.newlyStale > 0 || summary.cleared > 0) {
|
|
26390
|
+
console.log(
|
|
26391
|
+
`staleness watchdog: ${summary.newlyStale} newly stale, ${summary.cleared} cleared (${summary.stale}/${summary.scanned} stale).`
|
|
26392
|
+
);
|
|
26393
|
+
}
|
|
26394
|
+
} catch (err) {
|
|
26395
|
+
console.error("staleness watchdog tick failed:", err);
|
|
26396
|
+
}
|
|
26397
|
+
};
|
|
26398
|
+
void watchdogTick();
|
|
26399
|
+
stalenessWatchdogTimer = setInterval(() => void watchdogTick(), STALENESS_WATCHDOG_INTERVAL_MS);
|
|
26400
|
+
stalenessWatchdogTimer.unref?.();
|
|
26401
|
+
}
|
|
26010
26402
|
return new Promise((resolvePromise, reject) => {
|
|
26011
26403
|
server.on("error", (err) => {
|
|
26012
26404
|
if (err.code === "EADDRINUSE") {
|
|
@@ -26028,6 +26420,10 @@ function createDashboardServer(options) {
|
|
|
26028
26420
|
});
|
|
26029
26421
|
},
|
|
26030
26422
|
async stop() {
|
|
26423
|
+
if (stalenessWatchdogTimer) {
|
|
26424
|
+
clearInterval(stalenessWatchdogTimer);
|
|
26425
|
+
stalenessWatchdogTimer = null;
|
|
26426
|
+
}
|
|
26031
26427
|
await stopAutodiscovery();
|
|
26032
26428
|
await stopUsageCollector();
|
|
26033
26429
|
if (watcherHandle) {
|