bosun 0.41.8 → 0.41.10
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +1 -1
- package/README.md +23 -1
- package/agent/agent-event-bus.mjs +31 -2
- package/agent/agent-pool.mjs +275 -40
- package/agent/agent-prompts.mjs +9 -1
- package/agent/agent-supervisor.mjs +22 -0
- package/agent/autofix.mjs +1 -1
- package/agent/primary-agent.mjs +115 -5
- package/cli.mjs +3 -2
- package/config/config.mjs +47 -33
- package/config/context-shredding-config.mjs +1 -1
- package/config/repo-root.mjs +41 -33
- package/desktop/main.mjs +350 -25
- package/desktop/preload.cjs +8 -0
- package/desktop/preload.mjs +19 -0
- package/entrypoint.mjs +332 -0
- package/git/sdk-conflict-resolver.mjs +1 -1
- package/infra/health-status.mjs +72 -0
- package/infra/library-manager.mjs +58 -1
- package/infra/maintenance.mjs +1 -2
- package/infra/monitor.mjs +26 -8
- package/infra/session-tracker.mjs +30 -3
- package/package.json +12 -4
- package/server/bosun-mcp-server.mjs +1004 -0
- package/server/setup-web-server.mjs +288 -259
- package/server/ui-server.mjs +1323 -26
- package/shell/claude-shell.mjs +14 -1
- package/shell/codex-config.mjs +1 -1
- package/shell/codex-model-profiles.mjs +170 -30
- package/shell/codex-shell.mjs +63 -18
- package/shell/opencode-providers.mjs +20 -8
- package/task/task-executor.mjs +28 -0
- package/task/task-store.mjs +13 -4
- package/telegram/telegram-sentinel.mjs +54 -3
- package/tools/list-todos.mjs +7 -1
- package/ui/app.js +3 -2
- package/ui/components/agent-selector.js +127 -0
- package/ui/components/session-list.js +15 -10
- package/ui/demo-defaults.js +334 -336
- package/ui/modules/router.js +2 -0
- package/ui/modules/state.js +13 -5
- package/ui/tabs/chat.js +3 -0
- package/ui/tabs/library.js +284 -52
- package/ui/tabs/tasks.js +5 -13
- package/ui/tabs/workflows.js +766 -3
- package/workflow/workflow-engine.mjs +246 -5
- package/workflow/workflow-nodes/definitions.mjs +37 -0
- package/workflow/workflow-nodes.mjs +1014 -184
- package/workflow/workflow-templates.mjs +0 -5
- package/workflow-templates/_helpers.mjs +253 -0
- package/workflow-templates/agents.mjs +199 -226
- package/workflow-templates/github.mjs +106 -16
- package/workflow-templates/sub-workflows.mjs +233 -0
- package/workflow-templates/task-execution.mjs +125 -471
- package/workflow-templates/task-lifecycle.mjs +11 -48
- package/workspace/command-diagnostics.mjs +460 -0
- package/workspace/context-cache.mjs +396 -28
- package/workspace/worktree-manager.mjs +1 -1
|
@@ -187,6 +187,14 @@ async function getFs() {
|
|
|
187
187
|
return _fsPromises;
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
let _commandDiagnosticsMod = null;
|
|
191
|
+
async function getCommandDiagnosticsMod() {
|
|
192
|
+
if (!_commandDiagnosticsMod) {
|
|
193
|
+
_commandDiagnosticsMod = await import("./command-diagnostics.mjs");
|
|
194
|
+
}
|
|
195
|
+
return _commandDiagnosticsMod;
|
|
196
|
+
}
|
|
197
|
+
|
|
190
198
|
async function ensureCacheDir() {
|
|
191
199
|
const fs = await getFs();
|
|
192
200
|
await fs.mkdir(TOOL_LOG_DIR, { recursive: true });
|
|
@@ -463,14 +471,24 @@ function extractArgsPreview(item) {
|
|
|
463
471
|
}
|
|
464
472
|
|
|
465
473
|
const LIVE_TOOL_RETRIEVE_PLACEHOLDER = "__BOSUN_TOOL_LOG__";
|
|
466
|
-
const LIVE_ERROR_REGEX = /\b(error|errors|fatal|failed|failure|panic|traceback|exception|undefined|denied|not found|enoent|eacces)\b/i;
|
|
474
|
+
const LIVE_ERROR_REGEX = /\b(error|errors|fatal|failed|failure|panic|traceback|exception|undefined|denied|not found|enoent|eacces|segmentation fault|assertion|unhandled|stack trace|msb\d+|nu\d+|cs\d+|ts\d+)\b/i;
|
|
467
475
|
const LIVE_WARN_REGEX = /\b(warn|warning|deprecated)\b/i;
|
|
468
|
-
const LIVE_SUMMARY_REGEX = /\b(summary|total|totals|passed|failed|skipped|collected|found|matched|changed|insertions|deletions|done in|finished|ran \d+ tests?|test suites|packages? audited|up to date|build failed|completed)\b/i;
|
|
469
|
-
const LIVE_STATUS_REGEX = /^(FAIL|ERROR|warning|fatal|M\s|A\s|D\s|R\s|\?\?|@@|diff --git|--- |\+\+\+ )
|
|
476
|
+
const LIVE_SUMMARY_REGEX = /\b(summary|total|totals|passed|failed|skipped|collected|found|matched|changed|insertions|deletions|done in|finished|ran \d+ tests?|test suites|packages? audited|up to date|build failed|completed|build succeeded|test run|tests run|total tests|passed!|failed!|restore completed|restore failed|time elapsed)\b/i;
|
|
477
|
+
const LIVE_STATUS_REGEX = /^(FAIL|ERROR|warning|fatal|M\s|A\s|D\s|R\s|\?\?|@@|diff --git|--- |\+\+\+ |> |xUnit\.net|Test Run Failed|Test Run Successful|Failed!)/i;
|
|
470
478
|
const LIVE_STRUCTURED_FLAG_REGEX = /(^|\s)(--json|--format(?:=|\s+)json|-json\b|-o(?:=|\s+)json|--output(?:=|\s+)json|{{json\s+\.}})/i;
|
|
471
479
|
const LIVE_FILE_REF_REGEX = /((?:[A-Za-z]:)?[.~/\\\w-]+(?:[\\/][^:\s]+)+(?::\d+(?::\d+)?)?)/;
|
|
472
480
|
const LIVE_SHELL_WRAPPERS = new Set(["bash", "sh", "zsh", "pwsh", "powershell", "cmd"]);
|
|
473
481
|
const LIVE_ENV_WRAPPERS = new Set(["env", "command", "time", "nohup"]);
|
|
482
|
+
const GENERIC_SIGNAL_MARKER = "\n...[selected signal lines]...\n";
|
|
483
|
+
const GENERIC_OMITTED_MARKER = "\n...[middle content omitted]...\n";
|
|
484
|
+
|
|
485
|
+
function escapeRegExp(value) {
|
|
486
|
+
return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
function uniqueValues(values = []) {
|
|
490
|
+
return [...new Set(values)];
|
|
491
|
+
}
|
|
474
492
|
|
|
475
493
|
function extractCommandLine(item) {
|
|
476
494
|
if (!item || typeof item !== "object") return "";
|
|
@@ -588,7 +606,7 @@ function classifyLiveFamily(commandFamily, item) {
|
|
|
588
606
|
const full = `${cmd} ${extractCommandLine(item)}`.toLowerCase();
|
|
589
607
|
if (["grep", "rg", "find", "findstr", "select-string", "ag", "ack", "sift", "fd", "where", "which", "ls", "dir", "tree", "gci", "get-childitem"].includes(cmd) || /git\s+grep\b/.test(full)) return "search";
|
|
590
608
|
if (cmd === "git") return "git";
|
|
591
|
-
if (["go", "pytest", "cargo", "gradle", "maven", "mvn", "javac", "tsc", "jest", "vitest", "deno", "bun", "make", "cmake", "bazel", "buck", "nx", "turbo", "rush"].includes(cmd)) return "build";
|
|
609
|
+
if (["go", "pytest", "cargo", "gradle", "maven", "mvn", "javac", "tsc", "jest", "vitest", "deno", "bun", "make", "cmake", "bazel", "buck", "nx", "turbo", "rush", "dotnet", "msbuild", "xunit", "nunit", "ctest"].includes(cmd)) return "build";
|
|
592
610
|
if (["npm", "pnpm", "yarn", "node", "python", "python3", "pip", "pip3", "poetry", "composer", "bundle", "npx"].includes(cmd)) {
|
|
593
611
|
return /test|build|install|lint|run|pytest|jest|vitest|mocha|ava|unittest|coverage|compile/.test(full) ? "build" : "generic";
|
|
594
612
|
}
|
|
@@ -639,12 +657,103 @@ function buildSearchFileSummary(lines) {
|
|
|
639
657
|
.map(([file, count]) => `${file} (${count})`);
|
|
640
658
|
}
|
|
641
659
|
|
|
660
|
+
function normalizeSearchTerm(value) {
|
|
661
|
+
return String(value || "").replace(/^['"`]+|['"`]+$/g, "").trim();
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
function isHighSignalSearchTerm(term) {
|
|
665
|
+
const normalized = normalizeSearchTerm(term);
|
|
666
|
+
if (normalized.length < 4) return false;
|
|
667
|
+
if (/^(error|errors|failed|failure|exception|warning|warnings|runtime|type|assertion|network|permission|configuration)$/i.test(normalized)) {
|
|
668
|
+
return false;
|
|
669
|
+
}
|
|
670
|
+
return true;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
function scoreSearchTerm(term) {
|
|
674
|
+
const normalized = normalizeSearchTerm(term);
|
|
675
|
+
let score = normalized.length;
|
|
676
|
+
if (/^[A-Z][A-Z0-9_]{2,}$/.test(normalized)) score += 80;
|
|
677
|
+
if (/^(TS|CS|MSB|NU)\d+$/i.test(normalized)) score += 70;
|
|
678
|
+
if (normalized.includes("/") || normalized.includes("\\")) score += 50;
|
|
679
|
+
if (/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\b/.test(normalized)) score += 40;
|
|
680
|
+
if (/['"`]/.test(term)) score += 30;
|
|
681
|
+
return score;
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
function collectCandidateSearchTerms(value) {
|
|
685
|
+
const normalized = String(value || "").trim();
|
|
686
|
+
if (!normalized) return [];
|
|
687
|
+
const candidates = [];
|
|
688
|
+
|
|
689
|
+
for (const match of normalized.matchAll(/['"`]([^'"`]{4,})['"`]/g)) {
|
|
690
|
+
candidates.push(match[1]);
|
|
691
|
+
}
|
|
692
|
+
for (const match of normalized.matchAll(/\b(?:TS|CS|MSB|NU)\d+\b/gi)) {
|
|
693
|
+
candidates.push(match[0]);
|
|
694
|
+
}
|
|
695
|
+
for (const match of normalized.matchAll(/\b[A-Z][A-Z0-9_]{2,}\b/g)) {
|
|
696
|
+
candidates.push(match[0]);
|
|
697
|
+
}
|
|
698
|
+
for (const match of normalized.matchAll(/\b(?:[A-Za-z0-9_.-]+\/)+[A-Za-z0-9_.-]+\b/g)) {
|
|
699
|
+
candidates.push(match[0]);
|
|
700
|
+
}
|
|
701
|
+
for (const match of normalized.matchAll(/\b[A-Za-z0-9_.-]+\.[A-Za-z0-9_.-]+\b/g)) {
|
|
702
|
+
candidates.push(match[0]);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const detail = normalized.split(":").slice(1).join(":").trim();
|
|
706
|
+
if (detail.length >= 8) candidates.push(detail);
|
|
707
|
+
|
|
708
|
+
return uniqueValues(candidates)
|
|
709
|
+
.filter(isHighSignalSearchTerm)
|
|
710
|
+
.sort((left, right) => scoreSearchTerm(right) - scoreSearchTerm(left))
|
|
711
|
+
.slice(0, 6);
|
|
712
|
+
}
|
|
713
|
+
|
|
714
|
+
function collectDiagnosticSearchTerms(lines, family) {
|
|
715
|
+
const sourceLines = lines.filter((line) =>
|
|
716
|
+
LIVE_ERROR_REGEX.test(line) ||
|
|
717
|
+
LIVE_STATUS_REGEX.test(line) ||
|
|
718
|
+
LIVE_SUMMARY_REGEX.test(line) ||
|
|
719
|
+
(family === "build" && !!extractFileKey(line))
|
|
720
|
+
);
|
|
721
|
+
return uniqueValues(
|
|
722
|
+
sourceLines.flatMap((line) => collectCandidateSearchTerms(line)),
|
|
723
|
+
).slice(0, 10);
|
|
724
|
+
}
|
|
725
|
+
|
|
726
|
+
function collectExactSignalLines(input) {
|
|
727
|
+
const deduped = new Set();
|
|
728
|
+
for (const line of String(input || "").split("\n")) {
|
|
729
|
+
const trimmed = line.trimEnd();
|
|
730
|
+
if (!trimmed) continue;
|
|
731
|
+
if (LIVE_ERROR_REGEX.test(trimmed) || LIVE_WARN_REGEX.test(trimmed) || LIVE_SUMMARY_REGEX.test(trimmed) || LIVE_STATUS_REGEX.test(trimmed) || extractFileKey(trimmed)) {
|
|
732
|
+
deduped.add(trimmed);
|
|
733
|
+
}
|
|
734
|
+
}
|
|
735
|
+
return [...deduped].slice(0, 120);
|
|
736
|
+
}
|
|
737
|
+
|
|
738
|
+
function isLowValuePassLine(line) {
|
|
739
|
+
const normalized = String(line || "").trim();
|
|
740
|
+
if (!normalized) return false;
|
|
741
|
+
return /^Passed!\s+\S+/i.test(normalized) || /^Test run for .+\.dll/i.test(normalized);
|
|
742
|
+
}
|
|
743
|
+
|
|
642
744
|
function pickSelectedLines(lines, family, mode) {
|
|
643
745
|
const maxLines = mode === "aggressive" ? 12 : 20;
|
|
644
746
|
const errorSet = collectSignalIndices(lines, (line) => LIVE_ERROR_REGEX.test(line) || LIVE_STATUS_REGEX.test(line), family === "build" ? 1 : 0, mode === "aggressive" ? 8 : 12);
|
|
645
747
|
const warnSet = collectSignalIndices(lines, (line) => LIVE_WARN_REGEX.test(line), 0, mode === "aggressive" ? 4 : 6);
|
|
646
|
-
const summarySet = collectSignalIndices(lines, (line) => LIVE_SUMMARY_REGEX.test(line), 0, mode === "aggressive" ? 5 : 8);
|
|
748
|
+
const summarySet = collectSignalIndices(lines, (line) => LIVE_SUMMARY_REGEX.test(line) && !isLowValuePassLine(line), 0, mode === "aggressive" ? 5 : 8);
|
|
647
749
|
const fileSet = collectSignalIndices(lines, (line) => !!extractFileKey(line), 0, family === "search" ? maxLines : 8);
|
|
750
|
+
const diagnosticTerms = collectDiagnosticSearchTerms(lines, family);
|
|
751
|
+
const termSet = collectSignalIndices(
|
|
752
|
+
lines,
|
|
753
|
+
(line) => diagnosticTerms.some((term) => new RegExp(escapeRegExp(term), "i").test(line)),
|
|
754
|
+
family === "build" ? 1 : 0,
|
|
755
|
+
mode === "aggressive" ? 8 : 12,
|
|
756
|
+
);
|
|
648
757
|
const selected = new Set();
|
|
649
758
|
const addIndices = (iterable) => {
|
|
650
759
|
for (const idx of iterable) {
|
|
@@ -654,6 +763,7 @@ function pickSelectedLines(lines, family, mode) {
|
|
|
654
763
|
addIndices(errorSet);
|
|
655
764
|
addIndices(warnSet);
|
|
656
765
|
addIndices(summarySet);
|
|
766
|
+
addIndices(termSet);
|
|
657
767
|
if (family === "search" || family === "git") addIndices(fileSet);
|
|
658
768
|
if ((family === "logs" || family === "ops") && selected.size < maxLines) {
|
|
659
769
|
for (let i = Math.max(0, lines.length - (mode === "aggressive" ? 8 : 12)); i < lines.length && selected.size < maxLines; i += 1) {
|
|
@@ -673,6 +783,75 @@ function pickSelectedLines(lines, family, mode) {
|
|
|
673
783
|
return [...selected].sort((a, b) => a - b).map((idx) => lines[idx]).filter(Boolean);
|
|
674
784
|
}
|
|
675
785
|
|
|
786
|
+
function renderGenericSignalExcerptText(item, logRef, opts) {
|
|
787
|
+
const originalText = getItemText(item);
|
|
788
|
+
if (typeof originalText !== "string" || !originalText) return "";
|
|
789
|
+
const maxChars = Math.max(
|
|
790
|
+
Number(opts?.liveToolCompactionTargetChars) || 1800,
|
|
791
|
+
900,
|
|
792
|
+
);
|
|
793
|
+
if (originalText.length <= maxChars) return originalText;
|
|
794
|
+
|
|
795
|
+
const signalLines = collectExactSignalLines(originalText).join("\n");
|
|
796
|
+
let headChars = Math.min(Math.max(240, Math.floor(maxChars * 0.5)), maxChars);
|
|
797
|
+
let tailChars = Math.min(Math.max(160, Math.floor(maxChars * 0.2)), maxChars);
|
|
798
|
+
|
|
799
|
+
while (headChars + tailChars + GENERIC_OMITTED_MARKER.length > maxChars) {
|
|
800
|
+
if (headChars >= tailChars && headChars > 120) {
|
|
801
|
+
headChars = Math.max(120, headChars - 80);
|
|
802
|
+
} else if (tailChars > 80) {
|
|
803
|
+
tailChars = Math.max(80, tailChars - 80);
|
|
804
|
+
} else {
|
|
805
|
+
break;
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const head = originalText.slice(0, headChars);
|
|
810
|
+
const tail = originalText.slice(originalText.length - tailChars);
|
|
811
|
+
const retrieve = `bosun --tool-log ${logRef}`;
|
|
812
|
+
const budget =
|
|
813
|
+
maxChars -
|
|
814
|
+
head.length -
|
|
815
|
+
tail.length -
|
|
816
|
+
GENERIC_OMITTED_MARKER.length -
|
|
817
|
+
retrieve.length -
|
|
818
|
+
64;
|
|
819
|
+
|
|
820
|
+
const signalSnippet = budget > 0 && signalLines
|
|
821
|
+
? signalLines.slice(0, Math.max(0, budget))
|
|
822
|
+
: "";
|
|
823
|
+
|
|
824
|
+
const note = `\n\n[Signal-first excerpt — full output: ${retrieve}]`;
|
|
825
|
+
return [
|
|
826
|
+
head,
|
|
827
|
+
GENERIC_OMITTED_MARKER,
|
|
828
|
+
signalSnippet ? `${GENERIC_SIGNAL_MARKER}${signalSnippet}` : "",
|
|
829
|
+
tail,
|
|
830
|
+
note,
|
|
831
|
+
]
|
|
832
|
+
.join("")
|
|
833
|
+
.slice(0, maxChars + note.length)
|
|
834
|
+
.trimEnd();
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
function appendCommandDiagnosticFooter(baseText, diagnostic, opts) {
|
|
838
|
+
if (!diagnostic || typeof diagnostic !== "object") return baseText;
|
|
839
|
+
const { renderCommandDiagnosticFooter } = diagnostic._helpers || {};
|
|
840
|
+
const footerText = typeof renderCommandDiagnosticFooter === "function"
|
|
841
|
+
? renderCommandDiagnosticFooter(diagnostic)
|
|
842
|
+
: "";
|
|
843
|
+
if (!footerText) return baseText;
|
|
844
|
+
const maxChars = Math.max(
|
|
845
|
+
Number(opts?.liveToolCompactionTargetChars) || 1800,
|
|
846
|
+
900,
|
|
847
|
+
);
|
|
848
|
+
const footer = `\n\n${footerText}`;
|
|
849
|
+
const combined = `${String(baseText || "").trimEnd()}${footer}`;
|
|
850
|
+
if (combined.length <= maxChars + 320) return combined;
|
|
851
|
+
const headBudget = Math.max(240, maxChars - footer.length - 80);
|
|
852
|
+
return `${String(baseText || "").slice(0, headBudget).trimEnd()}\n\n[...summary trimmed for diagnostics...]\n\n${footerText}`;
|
|
853
|
+
}
|
|
854
|
+
|
|
676
855
|
function renderLiveCompactionText(analysis, logRef, opts) {
|
|
677
856
|
const retrieve = `bosun --tool-log ${logRef}`;
|
|
678
857
|
const header = `[Live-compacted ${analysis.family}] ${analysis.commandLabel} -> ${analysis.lineCount} lines / ${charLabel(analysis.originalText)}, saved ~${analysis.savedPct}% | Full output: ${retrieve}`;
|
|
@@ -717,12 +896,40 @@ function renderLiveCompactionText(analysis, logRef, opts) {
|
|
|
717
896
|
return rendered;
|
|
718
897
|
}
|
|
719
898
|
|
|
899
|
+
async function analyzeCommandDiagnosticForItem(item, logId = null) {
|
|
900
|
+
const text = getItemText(item);
|
|
901
|
+
if (!text) return null;
|
|
902
|
+
const { analyzeCommandDiagnostic, renderCommandDiagnosticFooter } = await getCommandDiagnosticsMod();
|
|
903
|
+
const diagnostic = await analyzeCommandDiagnostic({
|
|
904
|
+
command: extractCommandLine(item),
|
|
905
|
+
args: Array.isArray(item?.arguments?.argv)
|
|
906
|
+
? item.arguments.argv
|
|
907
|
+
: Array.isArray(item?.args)
|
|
908
|
+
? item.args
|
|
909
|
+
: [],
|
|
910
|
+
output: item?.aggregated_output ?? item?.output ?? item?.text ?? item?.stdout ?? "",
|
|
911
|
+
stderr: item?.stderr ?? "",
|
|
912
|
+
exitCode: item?.exit_code ?? item?.exitCode ?? 0,
|
|
913
|
+
});
|
|
914
|
+
if (!diagnostic) return null;
|
|
915
|
+
return {
|
|
916
|
+
...diagnostic,
|
|
917
|
+
retrieveCommand: logId ? `bosun --tool-log ${logId}` : null,
|
|
918
|
+
_helpers: { renderCommandDiagnosticFooter },
|
|
919
|
+
};
|
|
920
|
+
}
|
|
921
|
+
|
|
720
922
|
function analyzeLiveToolOutput(item, opts) {
|
|
721
923
|
const originalText = getItemText(item);
|
|
722
924
|
if (typeof originalText !== "string" || originalText.length < (opts.liveToolCompactionMinChars ?? 4000)) return null;
|
|
723
925
|
const commandFamily = extractCommandFamily(item);
|
|
724
926
|
const allowlist = new Set((opts.liveToolCompactionAllowCommands || []).map((value) => String(value).trim().toLowerCase()).filter(Boolean));
|
|
725
|
-
|
|
927
|
+
const enforceAllowlist =
|
|
928
|
+
allowlist.size > 0 &&
|
|
929
|
+
!allowlist.has(commandFamily) &&
|
|
930
|
+
item?.type !== "command_execution" &&
|
|
931
|
+
item?.type !== "command_output";
|
|
932
|
+
if (enforceAllowlist) return null;
|
|
726
933
|
if (opts.liveToolCompactionBlockStructured !== false && isLikelyStructuredOutput(originalText, item)) return null;
|
|
727
934
|
const family = classifyLiveFamily(commandFamily, item);
|
|
728
935
|
const normalizedText = originalText.replace(/\r\n?/g, "\n");
|
|
@@ -782,34 +989,62 @@ function shouldApplyLiveCompaction(item, opts, contextUsagePct, force = false) {
|
|
|
782
989
|
return runtimeReady && (sizePressure || contextPressure);
|
|
783
990
|
}
|
|
784
991
|
|
|
785
|
-
async function
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
992
|
+
async function compactStandaloneToolItem(
|
|
993
|
+
item,
|
|
994
|
+
opts = {},
|
|
995
|
+
{ agentType = null } = {},
|
|
996
|
+
) {
|
|
997
|
+
if (!item || typeof item !== "object") return item;
|
|
998
|
+
const existingText = getItemText(item);
|
|
999
|
+
if (typeof existingText !== "string" || existingText.length < (opts.liveToolCompactionMinChars ?? 4000)) {
|
|
1000
|
+
return item;
|
|
1001
|
+
}
|
|
1002
|
+
if (opts.liveToolCompactionBlockStructured !== false && isLikelyStructuredOutput(existingText, item)) {
|
|
1003
|
+
return item;
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
const directGitClass = classifyImmediateGitOutput(item, opts);
|
|
1007
|
+
if (directGitClass) {
|
|
1008
|
+
const logId = await writeToCache(item, extractToolName(item), extractArgsPreview(item));
|
|
1009
|
+
const compactedItem = applyImmediateGitCompression(item, logId, opts);
|
|
1010
|
+
const diagnostic = await analyzeCommandDiagnosticForItem(item, logId);
|
|
1011
|
+
if (diagnostic) {
|
|
1012
|
+
compactedItem._commandDiagnostics = diagnostic;
|
|
1013
|
+
setItemText(compactedItem, appendCommandDiagnosticFooter(getItemText(compactedItem), diagnostic, opts));
|
|
802
1014
|
}
|
|
1015
|
+
recordShreddingEvent({
|
|
1016
|
+
originalChars: existingText.length,
|
|
1017
|
+
compressedChars: getItemText(compactedItem).length,
|
|
1018
|
+
savedChars: Math.max(0, existingText.length - getItemText(compactedItem).length),
|
|
1019
|
+
savedPct: Math.max(0, Math.round(((existingText.length - getItemText(compactedItem).length) / Math.max(1, existingText.length)) * 100)),
|
|
1020
|
+
agentType: agentType || null,
|
|
1021
|
+
stage: "live_tool_compaction",
|
|
1022
|
+
compactionFamily: "git",
|
|
1023
|
+
commandFamily: extractCommandFamily(item),
|
|
1024
|
+
});
|
|
1025
|
+
return compactedItem;
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
const analysis = analyzeLiveToolOutput(item, opts);
|
|
1029
|
+
if (analysis) {
|
|
803
1030
|
const logId = await writeToCache(item, extractToolName(item), extractArgsPreview(item));
|
|
1031
|
+
const diagnostic = await analyzeCommandDiagnosticForItem(item, logId);
|
|
804
1032
|
const compactedItem = {
|
|
805
1033
|
...item,
|
|
806
1034
|
_cachedLogId: logId,
|
|
807
1035
|
_liveCompacted: true,
|
|
808
1036
|
_liveCompactionFamily: analysis.family,
|
|
1037
|
+
_liveCompactionCommandFamily: analysis.commandFamily,
|
|
809
1038
|
};
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
1039
|
+
if (diagnostic) compactedItem._commandDiagnostics = diagnostic;
|
|
1040
|
+
setItemText(
|
|
1041
|
+
compactedItem,
|
|
1042
|
+
appendCommandDiagnosticFooter(
|
|
1043
|
+
renderLiveCompactionText(analysis, logId, opts),
|
|
1044
|
+
diagnostic,
|
|
1045
|
+
opts,
|
|
1046
|
+
),
|
|
1047
|
+
);
|
|
813
1048
|
recordShreddingEvent({
|
|
814
1049
|
originalChars: analysis.originalText.length,
|
|
815
1050
|
compressedChars: getItemText(compactedItem).length,
|
|
@@ -820,9 +1055,136 @@ async function maybeCompactLiveToolOutputs(items, opts = {}, { contextUsagePct =
|
|
|
820
1055
|
compactionFamily: analysis.family,
|
|
821
1056
|
commandFamily: analysis.commandFamily,
|
|
822
1057
|
});
|
|
1058
|
+
return compactedItem;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
1061
|
+
const logId = await writeToCache(item, extractToolName(item), extractArgsPreview(item));
|
|
1062
|
+
const diagnostic = await analyzeCommandDiagnosticForItem(item, logId);
|
|
1063
|
+
const compactedItem = {
|
|
1064
|
+
...item,
|
|
1065
|
+
_cachedLogId: logId,
|
|
1066
|
+
_liveCompacted: true,
|
|
1067
|
+
_liveCompactionFamily: "generic",
|
|
1068
|
+
_liveCompactionCommandFamily: extractCommandFamily(item),
|
|
1069
|
+
};
|
|
1070
|
+
if (diagnostic) compactedItem._commandDiagnostics = diagnostic;
|
|
1071
|
+
setItemText(
|
|
1072
|
+
compactedItem,
|
|
1073
|
+
appendCommandDiagnosticFooter(
|
|
1074
|
+
renderGenericSignalExcerptText(item, logId, opts),
|
|
1075
|
+
diagnostic,
|
|
1076
|
+
opts,
|
|
1077
|
+
),
|
|
1078
|
+
);
|
|
1079
|
+
const compactedText = getItemText(compactedItem);
|
|
1080
|
+
if (!compactedText || compactedText.length >= existingText.length) {
|
|
1081
|
+
return item;
|
|
1082
|
+
}
|
|
1083
|
+
recordShreddingEvent({
|
|
1084
|
+
originalChars: existingText.length,
|
|
1085
|
+
compressedChars: compactedText.length,
|
|
1086
|
+
savedChars: Math.max(0, existingText.length - compactedText.length),
|
|
1087
|
+
savedPct: Math.max(0, Math.round(((existingText.length - compactedText.length) / Math.max(1, existingText.length)) * 100)),
|
|
1088
|
+
agentType: agentType || null,
|
|
1089
|
+
stage: "live_tool_compaction",
|
|
1090
|
+
compactionFamily: "generic",
|
|
1091
|
+
commandFamily: extractCommandFamily(item),
|
|
1092
|
+
});
|
|
1093
|
+
return compactedItem;
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
async function maybeCompactLiveToolOutputs(items, opts = {}, { contextUsagePct = null, force = false, agentType = null } = {}) {
|
|
1097
|
+
if (!Array.isArray(items) || items.length === 0) return items;
|
|
1098
|
+
let changed = false;
|
|
1099
|
+
const nextItems = [];
|
|
1100
|
+
for (const item of items) {
|
|
1101
|
+
if (classifyItem(item) !== "tool_output") {
|
|
1102
|
+
nextItems.push(item);
|
|
1103
|
+
continue;
|
|
1104
|
+
}
|
|
1105
|
+
if (!shouldApplyLiveCompaction(item, opts, contextUsagePct, force)) {
|
|
1106
|
+
nextItems.push(item);
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1109
|
+
const compactedItem = await compactStandaloneToolItem(item, opts, { agentType });
|
|
1110
|
+
nextItems.push(compactedItem);
|
|
1111
|
+
changed = changed || compactedItem !== item;
|
|
823
1112
|
}
|
|
824
1113
|
return changed ? nextItems : items;
|
|
825
1114
|
}
|
|
1115
|
+
|
|
1116
|
+
export async function compactCommandOutputPayload(
|
|
1117
|
+
payload = {},
|
|
1118
|
+
{
|
|
1119
|
+
sessionType = "flow",
|
|
1120
|
+
agentType = "workflow",
|
|
1121
|
+
force = true,
|
|
1122
|
+
} = {},
|
|
1123
|
+
) {
|
|
1124
|
+
const outputText =
|
|
1125
|
+
typeof payload.output === "string"
|
|
1126
|
+
? payload.output
|
|
1127
|
+
: typeof payload.stdout === "string"
|
|
1128
|
+
? payload.stdout
|
|
1129
|
+
: "";
|
|
1130
|
+
const stderrText = typeof payload.stderr === "string" ? payload.stderr : "";
|
|
1131
|
+
const trimmedOutput = outputText.trim();
|
|
1132
|
+
const trimmedStderr = stderrText.trim();
|
|
1133
|
+
const combinedText = [trimmedOutput, trimmedStderr && trimmedStderr !== trimmedOutput ? `[stderr]\n${trimmedStderr}` : ""]
|
|
1134
|
+
.filter(Boolean)
|
|
1135
|
+
.join("\n\n")
|
|
1136
|
+
.trim();
|
|
1137
|
+
|
|
1138
|
+
if (!combinedText) {
|
|
1139
|
+
return {
|
|
1140
|
+
text: "",
|
|
1141
|
+
compacted: false,
|
|
1142
|
+
originalChars: 0,
|
|
1143
|
+
compactedChars: 0,
|
|
1144
|
+
item: null,
|
|
1145
|
+
toolLogId: null,
|
|
1146
|
+
retrieveCommand: null,
|
|
1147
|
+
compactionFamily: null,
|
|
1148
|
+
commandFamily: null,
|
|
1149
|
+
};
|
|
1150
|
+
}
|
|
1151
|
+
|
|
1152
|
+
const syntheticItem = {
|
|
1153
|
+
type: "command_execution",
|
|
1154
|
+
tool_name: payload.toolName || "command_execution",
|
|
1155
|
+
command: String(payload.command || "").trim(),
|
|
1156
|
+
arguments: Array.isArray(payload.args)
|
|
1157
|
+
? { argv: payload.args.map((value) => String(value)) }
|
|
1158
|
+
: payload.args,
|
|
1159
|
+
aggregated_output: combinedText,
|
|
1160
|
+
exit_code: Number.isFinite(Number(payload.exitCode)) ? Number(payload.exitCode) : undefined,
|
|
1161
|
+
duration_ms: Number.isFinite(Number(payload.durationMs)) ? Number(payload.durationMs) : undefined,
|
|
1162
|
+
};
|
|
1163
|
+
|
|
1164
|
+
const compactedItems = await maybeCompressSessionItems([syntheticItem], {
|
|
1165
|
+
sessionType,
|
|
1166
|
+
agentType,
|
|
1167
|
+
force,
|
|
1168
|
+
skip: false,
|
|
1169
|
+
});
|
|
1170
|
+
const compactedItem = compactedItems[0] || syntheticItem;
|
|
1171
|
+
const compactedText = String(getItemText(compactedItem) || combinedText).trim();
|
|
1172
|
+
const toolLogId = compactedItem?._cachedLogId || null;
|
|
1173
|
+
const commandDiagnostics = compactedItem?._commandDiagnostics || null;
|
|
1174
|
+
|
|
1175
|
+
return {
|
|
1176
|
+
text: compactedText,
|
|
1177
|
+
compacted: compactedText.length < combinedText.length,
|
|
1178
|
+
originalChars: combinedText.length,
|
|
1179
|
+
compactedChars: compactedText.length,
|
|
1180
|
+
item: compactedItem,
|
|
1181
|
+
toolLogId,
|
|
1182
|
+
retrieveCommand: toolLogId ? `bosun --tool-log ${toolLogId}` : null,
|
|
1183
|
+
compactionFamily: compactedItem?._liveCompactionFamily || null,
|
|
1184
|
+
commandFamily: compactedItem?._liveCompactionCommandFamily || extractCommandFamily(syntheticItem),
|
|
1185
|
+
commandDiagnostics,
|
|
1186
|
+
};
|
|
1187
|
+
}
|
|
826
1188
|
function extractCommandText(item) {
|
|
827
1189
|
if (!item || typeof item !== "object") return "";
|
|
828
1190
|
|
|
@@ -953,7 +1315,14 @@ function compressImmediateGitText(text, logId, opts) {
|
|
|
953
1315
|
}
|
|
954
1316
|
|
|
955
1317
|
function applyImmediateGitCompression(item, logId, opts) {
|
|
956
|
-
const compressed = {
|
|
1318
|
+
const compressed = {
|
|
1319
|
+
...item,
|
|
1320
|
+
_cachedLogId: logId,
|
|
1321
|
+
_compressed: "git_tier2",
|
|
1322
|
+
_liveCompacted: true,
|
|
1323
|
+
_liveCompactionFamily: "git",
|
|
1324
|
+
_liveCompactionCommandFamily: extractCommandFamily(item),
|
|
1325
|
+
};
|
|
957
1326
|
setItemText(compressed, compressImmediateGitText(getItemText(item), logId, opts));
|
|
958
1327
|
return compressed;
|
|
959
1328
|
}
|
|
@@ -2461,4 +2830,3 @@ export async function maybeCompressSessionItems(
|
|
|
2461
2830
|
|
|
2462
2831
|
return compressedItems;
|
|
2463
2832
|
}
|
|
2464
|
-
|
|
@@ -599,7 +599,7 @@ class WorktreeManager {
|
|
|
599
599
|
const detection = detectProjectStack(worktreePath);
|
|
600
600
|
if (!detection?.primary) return;
|
|
601
601
|
|
|
602
|
-
const plan = buildBootstrapPlan(worktreePath, policy, detection);
|
|
602
|
+
const plan = buildBootstrapPlan(worktreePath, policy, detection, this.repoRoot);
|
|
603
603
|
ensureWorktreeSharedPaths(this.repoRoot, worktreePath, plan.sharedPaths);
|
|
604
604
|
|
|
605
605
|
const signature = buildBootstrapSignature(plan);
|