@mrciphersmith/keryx 0.3.133 → 0.3.134
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/README.md +6 -1
- package/dist/cli.js +1553 -920
- package/dist/core.js +10 -0
- package/package.json +2 -2
- package/src/gdskills/bundled/install-manifest.json +2 -1
- package/src/gdskills/bundled/skills/review/review-degradation/SKILL.md +193 -0
- package/src/gdskills/bundled/skills/review/review-orchestrator/SKILL.md +5 -5
- package/src/gdskills/bundled/skills/review/review-orchestrator/reviewer-finding.schema.json +12 -0
- package/src/gdskills/contracts/review-finding.schema.json +23 -0
package/dist/cli.js
CHANGED
|
@@ -15025,8 +15025,12 @@ function renderSecurityPrePushHook() {
|
|
|
15025
15025
|
# HEAD-only heuristic under-scanned a first push). Fall back to the tracked
|
|
15026
15026
|
# push/upstream range only when stdin yields nothing usable.
|
|
15027
15027
|
changed_files=""
|
|
15028
|
+
saw_ref=0
|
|
15029
|
+
destination_remote="$1"
|
|
15030
|
+
remote_tips="$(git for-each-ref --format='%(objectname)' "refs/remotes/$destination_remote/" 2>/dev/null)"
|
|
15028
15031
|
while read -r local_ref local_sha remote_ref remote_sha; do
|
|
15029
15032
|
[ -z "$local_ref" ] && continue
|
|
15033
|
+
saw_ref=1
|
|
15030
15034
|
# Skip deleted refs (local sha all-zero): there is nothing to scan.
|
|
15031
15035
|
case "$local_sha" in
|
|
15032
15036
|
*[!0]*) : ;;
|
|
@@ -15041,7 +15045,10 @@ function renderSecurityPrePushHook() {
|
|
|
15041
15045
|
# New ref: scan all commits unique to this push. Use the merge-base with
|
|
15042
15046
|
# any existing remote as the base, falling back to the empty tree when
|
|
15043
15047
|
# the branch shares no history with a remote (truly first push).
|
|
15044
|
-
remotes="
|
|
15048
|
+
remotes=""
|
|
15049
|
+
if [ -n "$destination_remote" ]; then
|
|
15050
|
+
remotes="$(printf '%s\\n' "$remote_tips" | tr '\\n' ' ')"
|
|
15051
|
+
fi
|
|
15045
15052
|
base=""
|
|
15046
15053
|
if [ -n "$remotes" ]; then
|
|
15047
15054
|
base="$(git merge-base "$local_sha" $remotes 2>/dev/null || true)"
|
|
@@ -15052,6 +15059,23 @@ function renderSecurityPrePushHook() {
|
|
|
15052
15059
|
range_files="$(git diff --name-only --diff-filter=ACMR "$base".."$local_sha" 2>/dev/null || true)"
|
|
15053
15060
|
;;
|
|
15054
15061
|
esac
|
|
15062
|
+
# Avoid rescanning content identical to a known tip on the destination.
|
|
15063
|
+
# Keep locally modified inherited files and never trust unrelated remotes.
|
|
15064
|
+
# Failed comparisons retain the candidates (conservative fallback).
|
|
15065
|
+
if [ -n "$destination_remote" ] && [ -n "$range_files" ]; then
|
|
15066
|
+
range_files="$(printf '%s\\n' "$range_files" | LC_ALL=C sort -u)"
|
|
15067
|
+
for published_tip in $remote_tips; do
|
|
15068
|
+
if unpublished="$(git diff --name-only --no-renames "$published_tip" "$local_sha" 2>/dev/null)" &&
|
|
15069
|
+
worktree_changes="$(git diff --name-only --no-renames "$local_sha" -- 2>/dev/null)"; then
|
|
15070
|
+
differences="$(printf '%s\\n%s\\n' "$unpublished" "$worktree_changes" | LC_ALL=C sort -u)"
|
|
15071
|
+
range_files="$(printf '%s\\n' "$range_files" | while IFS= read -r candidate; do
|
|
15072
|
+
if printf '%s\\n' "$differences" | grep -Fqx -- "$candidate"; then
|
|
15073
|
+
printf '%s\\n' "$candidate"
|
|
15074
|
+
fi
|
|
15075
|
+
done)"
|
|
15076
|
+
fi
|
|
15077
|
+
done
|
|
15078
|
+
fi
|
|
15055
15079
|
if [ -n "$range_files" ]; then
|
|
15056
15080
|
changed_files="$changed_files
|
|
15057
15081
|
$range_files"
|
|
@@ -15060,7 +15084,7 @@ $range_files"
|
|
|
15060
15084
|
|
|
15061
15085
|
# Fall back to the tracked push/upstream range (then HEAD) when stdin was empty
|
|
15062
15086
|
# (e.g. the hook was invoked manually without ref lines).
|
|
15063
|
-
if [
|
|
15087
|
+
if [ "$saw_ref" -eq 0 ]; then
|
|
15064
15088
|
range="HEAD"
|
|
15065
15089
|
push_ref="$(git rev-parse --abbrev-ref --symbolic-full-name '@{push}' 2>/dev/null || true)"
|
|
15066
15090
|
if [ -n "$push_ref" ]; then
|
|
@@ -15116,7 +15140,7 @@ $range_files"
|
|
|
15116
15140
|
return 0
|
|
15117
15141
|
}
|
|
15118
15142
|
|
|
15119
|
-
keryx_security_pre_push || exit $?
|
|
15143
|
+
keryx_security_pre_push "$@" || exit $?
|
|
15120
15144
|
`;
|
|
15121
15145
|
}
|
|
15122
15146
|
function renderGdwikiPostCommitHook() {
|
|
@@ -18853,6 +18877,12 @@ var init_catalog = __esm(() => {
|
|
|
18853
18877
|
"Anchor class_scope on the callers that break, never on the changed line.",
|
|
18854
18878
|
"Say when the radius could not answer \u2014 a non-code change has an empty one."
|
|
18855
18879
|
]),
|
|
18880
|
+
skill("review-degradation", "review", ["full"], "Ask whether the code is worse after a change than on the base branch (behaviour, defaults, speed, structure) and whether that can be shown, over the diff plus every consumer of each changed shared symbol.", [
|
|
18881
|
+
"Read the diff against the description before reading any consumer.",
|
|
18882
|
+
"Trace the consumers the code graph lists for each changed shared export; the set is the graph's, not yours.",
|
|
18883
|
+
"Name one degradation class and one evidence kind per finding; a major or blocker without evidence is lowered to info at ingest.",
|
|
18884
|
+
"Say what was traced when nothing was found, and say when the consumer cap dropped any."
|
|
18885
|
+
]),
|
|
18856
18886
|
skill("review-pr-contract", "review", ["recommended", "full"], "Compare what the PR body claims (checklist boxes, stated changes) with the diff, and look for documentation outside the diff that the change made stale.", [
|
|
18857
18887
|
"Work every checklist box in pr-context.md as a row: quoted box, state, quoted diff line, expectation.",
|
|
18858
18888
|
"File a finding only where a quoted box or claim disagrees with a quoted diff or document line.",
|
|
@@ -19546,6 +19576,7 @@ var init_skill_length_ceilings = __esm(() => {
|
|
|
19546
19576
|
"review/review-backend": 361,
|
|
19547
19577
|
"review/review-clean-code": 538,
|
|
19548
19578
|
"review/review-core-boundaries": 158,
|
|
19579
|
+
"review/review-degradation": 193,
|
|
19549
19580
|
"review/review-flow-graph": 183,
|
|
19550
19581
|
"review/review-frontend": 607,
|
|
19551
19582
|
"review/review-frontend-conventions": 206,
|
|
@@ -23371,7 +23402,7 @@ var package_default;
|
|
|
23371
23402
|
var init_package = __esm(() => {
|
|
23372
23403
|
package_default = {
|
|
23373
23404
|
name: "@mrciphersmith/keryx",
|
|
23374
|
-
version: "0.3.
|
|
23405
|
+
version: "0.3.134",
|
|
23375
23406
|
description: "Version-controlled project context for AI coding agents: code graph, architecture wiki, project memory, relevant tests, quality signals, and task flows.",
|
|
23376
23407
|
private: false,
|
|
23377
23408
|
publishConfig: {
|
|
@@ -23444,7 +23475,6 @@ var init_package = __esm(() => {
|
|
|
23444
23475
|
"README.md",
|
|
23445
23476
|
"package.json"
|
|
23446
23477
|
],
|
|
23447
|
-
dependencies: {},
|
|
23448
23478
|
overrides: {
|
|
23449
23479
|
"fast-uri": "^3.1.9",
|
|
23450
23480
|
"body-parser": "^2.4.0",
|
|
@@ -23456,6 +23486,7 @@ var init_package = __esm(() => {
|
|
|
23456
23486
|
protobufjs: "7.6.6",
|
|
23457
23487
|
sharp: "0.35.5"
|
|
23458
23488
|
},
|
|
23489
|
+
dependencies: {},
|
|
23459
23490
|
optionalDependencies: {
|
|
23460
23491
|
"@modelcontextprotocol/sdk": "^1.32.1",
|
|
23461
23492
|
"@opentui/core": "^0.4.5",
|
|
@@ -43837,7 +43868,7 @@ function isHostQuestion(value) {
|
|
|
43837
43868
|
function hostQuestionFields(source) {
|
|
43838
43869
|
return source?.hostQuestion === undefined ? {} : { hostQuestion: source.hostQuestion };
|
|
43839
43870
|
}
|
|
43840
|
-
var MANAGED_REVIEW_MODES, REVIEW_TARGET_KINDS, REVIEW_PACKAGE_STATUSES, REVIEW_COVERAGE_STATUSES, HOST_QUESTION_SOURCES, REVIEW_FINDING_SEVERITIES, REVIEW_FINDING_CONFIDENCES, FINDING_DISPOSITION_STATES, FINDING_DISMISSAL_STATES, EXTERNAL_TERMINAL_DISPOSITIONS, VERIFICATION_VERDICTS, VERIFICATION_METHODS, REASONING_CAPPED_VERDICT = "unverifiable", VERIFICATION_MODES, DEFAULT_VERIFICATION_MODE = "annotate", PREMISE_VERDICTS, HELD_PREMISE_VERDICTS;
|
|
43871
|
+
var MANAGED_REVIEW_MODES, REVIEW_TARGET_KINDS, REVIEW_PACKAGE_STATUSES, REVIEW_COVERAGE_STATUSES, HOST_QUESTION_SOURCES, REVIEW_FINDING_SEVERITIES, REVIEW_FINDING_CONFIDENCES, FINDING_DISPOSITION_STATES, FINDING_DISMISSAL_STATES, EXTERNAL_TERMINAL_DISPOSITIONS, VERIFICATION_VERDICTS, VERIFICATION_METHODS, REASONING_CAPPED_VERDICT = "unverifiable", VERIFICATION_MODES, DEFAULT_VERIFICATION_MODE = "annotate", PREMISE_VERDICTS, HELD_PREMISE_VERDICTS, REVIEW_DEGRADATION_CLASSES, REVIEW_EVIDENCE_KINDS;
|
|
43841
43872
|
var init_types4 = __esm(() => {
|
|
43842
43873
|
MANAGED_REVIEW_MODES = ["attach-review", "review-flow", "ingest"];
|
|
43843
43874
|
REVIEW_TARGET_KINDS = ["pr", "issue", "branch", "path", "report"];
|
|
@@ -43869,6 +43900,16 @@ var init_types4 = __esm(() => {
|
|
|
43869
43900
|
VERIFICATION_MODES = ["off", "annotate", "filter"];
|
|
43870
43901
|
PREMISE_VERDICTS = ["upheld", "downgraded", "challenged", "owner-decision"];
|
|
43871
43902
|
HELD_PREMISE_VERDICTS = ["challenged", "owner-decision"];
|
|
43903
|
+
REVIEW_DEGRADATION_CLASSES = [
|
|
43904
|
+
"behaviour-delta",
|
|
43905
|
+
"default-change",
|
|
43906
|
+
"diff-vs-description",
|
|
43907
|
+
"debt",
|
|
43908
|
+
"performance",
|
|
43909
|
+
"cross-pr",
|
|
43910
|
+
"blind-test"
|
|
43911
|
+
];
|
|
43912
|
+
REVIEW_EVIDENCE_KINDS = ["probe", "consumer-trace", "merge-trace", "red-test", "base-diff"];
|
|
43872
43913
|
});
|
|
43873
43914
|
|
|
43874
43915
|
// src/review/blast-radius.ts
|
|
@@ -50117,6 +50158,10 @@ function lightConventionsReviewer(roster, inventory) {
|
|
|
50117
50158
|
const group = Object.values(overlapGroupsOf(inventory)).find((members) => members.includes(pick));
|
|
50118
50159
|
return group?.find((member) => roster.includes(member)) ?? pick;
|
|
50119
50160
|
}
|
|
50161
|
+
async function installedReviewerNames(root) {
|
|
50162
|
+
const inventory = await collectReviewers(root);
|
|
50163
|
+
return new Set([...inventory.bundled, ...inventory.project].map((reviewer) => reviewer.name));
|
|
50164
|
+
}
|
|
50120
50165
|
async function requiredReviewers(root, mode, files, options = {}) {
|
|
50121
50166
|
const inventory = await collectReviewers(root);
|
|
50122
50167
|
const stack = await detectProjectStack(root);
|
|
@@ -51099,6 +51144,9 @@ function collapseOverlapGroups(roles, groups = BUNDLED_OVERLAP_GROUPS) {
|
|
|
51099
51144
|
function isRoutedManifest(manifest) {
|
|
51100
51145
|
return manifest.slices.some((slice) => slice.retryOf === undefined && Array.isArray(slice.roles));
|
|
51101
51146
|
}
|
|
51147
|
+
function changesSharedCodeCandidate(region) {
|
|
51148
|
+
return deepReasonOfFile(region.path) === "source code";
|
|
51149
|
+
}
|
|
51102
51150
|
function contentRolesOf(region) {
|
|
51103
51151
|
const changed = changedLinesOf(region.text);
|
|
51104
51152
|
const code = CODE_FILE2.test(region.path) && !isTestFilePath(region.path) && !DOC_FILE.test(region.path);
|
|
@@ -51223,7 +51271,7 @@ function renderRoutingSummary(summary) {
|
|
|
51223
51271
|
const roles = Object.entries(summary.byRole).sort(([a], [b]) => a.localeCompare(b)).map(([role, count]) => `${role} ${count}`).join(", ");
|
|
51224
51272
|
return `routing: ${summary.deep} deep (${summary.audit} audit), ${summary.light} light of ${summary.slices} slice(s); slices per role: ${roles || "none"}`;
|
|
51225
51273
|
}
|
|
51226
|
-
var ALWAYS_ROLE = "review-logic", REGRESSION_ROLE = "review-regression", DEEP_ROLES, DEEP_RISK_CLASSES, DEFAULT_REACH_THRESHOLD = 25, JEV_DEEP_THRESHOLD = 0.7, AUDIT_RATE = 0.125, CODE_FILE2, DOC_FILE, STYLE_FILE, FRONTEND_FILE, CONFIG_FILE, BACKEND_DIR2, DATA_FILE2, AGENT_INSTRUCTION_FILE, CONTRACT_FILE, SECURITY_PRINCIPLE, SOURCE_CODE_FILE, FIXTURE_FILE, GENERATED_FILE, ENTRY_FILE, ROLE_RULES, ROUTABLE_ROLES, BUNDLED_OVERLAP_GROUPS, ROLES_RUN_WITHOUT_SLICES, changedLinesOf = (text) => text.split(`
|
|
51274
|
+
var ALWAYS_ROLE = "review-logic", REGRESSION_ROLE = "review-regression", DEGRADATION_ROLE = "review-degradation", DEEP_ROLES, DEEP_RISK_CLASSES, DEFAULT_REACH_THRESHOLD = 25, JEV_DEEP_THRESHOLD = 0.7, AUDIT_RATE = 0.125, CODE_FILE2, DOC_FILE, STYLE_FILE, FRONTEND_FILE, CONFIG_FILE, BACKEND_DIR2, DATA_FILE2, AGENT_INSTRUCTION_FILE, CONTRACT_FILE, SECURITY_PRINCIPLE, SOURCE_CODE_FILE, FIXTURE_FILE, GENERATED_FILE, SHARED_SYMBOL_CHANGE, ENTRY_FILE, ROLE_RULES, ROUTABLE_ROLES, BUNDLED_OVERLAP_GROUPS, ROLES_RUN_WITHOUT_SLICES, changedLinesOf = (text) => text.split(`
|
|
51227
51275
|
`).filter((line) => line.startsWith("+") || line.startsWith("-")).join(`
|
|
51228
51276
|
`), auditKey = (head, id) => createHash43("sha256").update(`${head}\x00${id}`).digest("hex"), AUDIT_MIN_LIGHT = 4;
|
|
51229
51277
|
var init_slice_routing = __esm(() => {
|
|
@@ -51255,6 +51303,7 @@ var init_slice_routing = __esm(() => {
|
|
|
51255
51303
|
SOURCE_CODE_FILE = /\.(ts|tsx|js|jsx|mjs|cjs|mts|cts|py|go|rs|java|kt|kts|swift|rb|php|cs|c|cc|cpp|cxx|h|hh|hpp|m|mm|scala|lua|dart|ex|exs|sh|bash|zsh|sql|vue|svelte)$/i;
|
|
51256
51304
|
FIXTURE_FILE = /((^|\/)(__tests__|__snapshots__|__fixtures__|fixtures?|testdata|test-data)\/|\.snap$)/i;
|
|
51257
51305
|
GENERATED_FILE = /((^|\/)(dist|build|out|vendor|node_modules|generated|__generated__)\/|\.(min\.(js|css)|generated\.\w+|lock|lockb)$|(^|\/)(package-lock\.json|bun\.lockb?|yarn\.lock|pnpm-lock\.yaml)$)/i;
|
|
51306
|
+
SHARED_SYMBOL_CHANGE = /^[+-]\s*export\s+(\*|\{|default\b|declare\b|abstract\b|(async\s+)?(function|class|interface|type|const|let|var|enum)\b)/m;
|
|
51258
51307
|
ENTRY_FILE = /(^|\/)(index|mod|main)\.[cm]?[jt]sx?$/;
|
|
51259
51308
|
ROLE_RULES = [
|
|
51260
51309
|
{ role: "review-testing-practices", why: "test file", path: (f) => isTestFilePath(f) || BUNDLED_PATH_GATES["review-testing-practices"](f) || /\.stories\.[jt]sx?$/.test(f) },
|
|
@@ -51271,6 +51320,7 @@ var init_slice_routing = __esm(() => {
|
|
|
51271
51320
|
{ role: "review-architecture", why: "agent instructions (skill, rule, AGENTS/CLAUDE file, prompt)", path: (f) => AGENT_INSTRUCTION_FILE.test(f) },
|
|
51272
51321
|
{ role: "review-architecture", why: "contract or schema", path: (f) => CONTRACT_FILE.test(f) },
|
|
51273
51322
|
{ role: "review-architecture", why: "exported signature or contract type changed", content: /^[+-]\s*export\s+(declare\s+)?(type|interface|abstract\s+class|enum)\b|^[+-]\s*export\s+(async\s+)?function\s+\w+\s*[<(]/m },
|
|
51323
|
+
{ role: DEGRADATION_ROLE, why: "shared (exported) symbol changed", content: SHARED_SYMBOL_CHANGE, codeOnly: true },
|
|
51274
51324
|
{ role: "review-security-code", why: "process, argv, git, input-derived path, env or network", content: SECURITY_PRINCIPLE, codeOnly: true },
|
|
51275
51325
|
{ role: "review-security-code", why: "process, filesystem write or trust-sensitive code", content: /\b(exec|execSync|execFile|spawn|spawnSync|Bun\.spawn|child_process|eval|new Function|innerHTML|dangerouslySetInnerHTML|password|passwd|secret|token|credential|api[_-]?key|sanitiz\w*|escape\w*|redact\w*|untrusted|trust\w*|permission\w*|allow-?list|deny-?list|sandbox\w*|cookie|cors|csrf|jwt|writeFile\w*|rm|unlink|rmdir|chmod|symlink\w*|realpath)\b/i, codeOnly: true },
|
|
51276
51326
|
{ role: "review-security-code", why: "auth, crypto or secret path", path: (f) => /\b(auth|login|session|permission|acl|rbac|credential|crypto|cipher|secret|security|trust|sandbox)\w*/i.test(f) && !isTestFilePath(f) && !DOC_FILE.test(f) },
|
|
@@ -85865,9 +85915,12 @@ function fixRosterRoles(findingIds, prior) {
|
|
|
85865
85915
|
}
|
|
85866
85916
|
function fixContentRoles(text) {
|
|
85867
85917
|
const roles = new Set;
|
|
85868
|
-
for (const region of parseScopedDiff(text))
|
|
85918
|
+
for (const region of parseScopedDiff(text)) {
|
|
85869
85919
|
for (const hit of contentRolesOf(region))
|
|
85870
85920
|
roles.add(hit.role);
|
|
85921
|
+
if (changesSharedCodeCandidate(region))
|
|
85922
|
+
roles.add(DEGRADATION_ROLE);
|
|
85923
|
+
}
|
|
85871
85924
|
return collapseOverlapGroups(roles).sort();
|
|
85872
85925
|
}
|
|
85873
85926
|
function fixSliceRoster(findingIds, prior, text) {
|
|
@@ -86072,12 +86125,15 @@ function secondLookClaimBacked(entry, context, text) {
|
|
|
86072
86125
|
return false;
|
|
86073
86126
|
return everyChangedLine(text, (file, walked) => inSecondLookHunk(hunks, file, walked));
|
|
86074
86127
|
}
|
|
86075
|
-
function
|
|
86076
|
-
return
|
|
86128
|
+
function effectiveSliceRoles(slice, installed) {
|
|
86129
|
+
return (slice.roles ?? []).filter((role) => installed.has(role));
|
|
86077
86130
|
}
|
|
86078
|
-
function
|
|
86079
|
-
|
|
86080
|
-
|
|
86131
|
+
function fixSliceRoles(manifest, installed) {
|
|
86132
|
+
return [...new Set(manifest.slices.filter((slice) => slice.retryOf === undefined && (slice.kind === "fix" || slice.kind === "second-look")).flatMap((slice) => effectiveSliceRoles(slice, installed)))].sort();
|
|
86133
|
+
}
|
|
86134
|
+
function manifestRosterRoles(manifest, installed) {
|
|
86135
|
+
const routed = manifest.slices.filter((slice) => slice.retryOf === undefined).flatMap((slice) => effectiveSliceRoles(slice, installed).filter((role) => ALWAYS_DISPATCHED_ROUTED_ROLES.has(role)));
|
|
86136
|
+
return [...new Set([...fixSliceRoles(manifest, installed), ...routed])].sort();
|
|
86081
86137
|
}
|
|
86082
86138
|
function splitCounts(manifest) {
|
|
86083
86139
|
const originals = manifest.slices.filter((slice) => slice.retryOf === undefined);
|
|
@@ -86106,13 +86162,13 @@ function priorDispositionsOf(result) {
|
|
|
86106
86162
|
}
|
|
86107
86163
|
return out;
|
|
86108
86164
|
}
|
|
86109
|
-
function fixSliceCoverage(manifest, readBy, dispositionsBy, skipped
|
|
86165
|
+
function fixSliceCoverage(manifest, readBy, dispositionsBy, skipped, installed) {
|
|
86110
86166
|
const covered = [];
|
|
86111
86167
|
const gaps = [];
|
|
86112
86168
|
for (const slice of manifest.slices) {
|
|
86113
86169
|
if (slice.retryOf !== undefined || slice.kind !== "fix")
|
|
86114
86170
|
continue;
|
|
86115
|
-
const roster = (slice
|
|
86171
|
+
const roster = effectiveSliceRoles(slice, installed).filter((role) => !skipped.has(role));
|
|
86116
86172
|
const ids = slice.findingIds ?? [];
|
|
86117
86173
|
const unread = roster.filter((role) => readBy.get(role)?.has(slice.id) !== true);
|
|
86118
86174
|
const undisposed = ids.filter((id) => !roster.some((role) => dispositionsBy.get(role)?.has(id) === true));
|
|
@@ -86177,6 +86233,15 @@ var init_round_split = __esm(() => {
|
|
|
86177
86233
|
ALWAYS_DISPATCHED_ROUTED_ROLES = new Set(["review-security-code"]);
|
|
86178
86234
|
});
|
|
86179
86235
|
|
|
86236
|
+
// src/review/roster.ts
|
|
86237
|
+
async function installedRosterRoles(root, manifest) {
|
|
86238
|
+
return manifestRosterRoles(manifest, await installedReviewerNames(root));
|
|
86239
|
+
}
|
|
86240
|
+
var init_roster = __esm(() => {
|
|
86241
|
+
init_coverage();
|
|
86242
|
+
init_round_split();
|
|
86243
|
+
});
|
|
86244
|
+
|
|
86180
86245
|
// src/review/estimate.ts
|
|
86181
86246
|
import { createHash as createHash57 } from "crypto";
|
|
86182
86247
|
import { existsSync as existsSync34, readFileSync as readFileSync20 } from "fs";
|
|
@@ -86357,7 +86422,7 @@ async function estimatePackage(packageDir, options = {}) {
|
|
|
86357
86422
|
if (problem !== undefined)
|
|
86358
86423
|
throw new Error(`${path204.relative(root, found.file)}: ${problem}`);
|
|
86359
86424
|
const thresholds = await readPlanThresholds(root);
|
|
86360
|
-
const roles = [...new Set([...set.required, ...found === undefined ? [] :
|
|
86425
|
+
const roles = [...new Set([...set.required, ...found === undefined ? [] : await installedRosterRoles(root, found.manifest)])].sort();
|
|
86361
86426
|
return { ...estimateRound(roles, found?.manifest, recorded?.depth ?? "unrecorded", thresholds), manifest: found === undefined ? undefined : path204.relative(root, found.file) };
|
|
86362
86427
|
}
|
|
86363
86428
|
function renderEstimate(estimate) {
|
|
@@ -86385,6 +86450,7 @@ var init_estimate = __esm(() => {
|
|
|
86385
86450
|
init_fs();
|
|
86386
86451
|
init_json();
|
|
86387
86452
|
init_coverage();
|
|
86453
|
+
init_roster();
|
|
86388
86454
|
init_role_switches();
|
|
86389
86455
|
init_run_profile();
|
|
86390
86456
|
init_slice();
|
|
@@ -86704,7 +86770,7 @@ async function packageReviewers(packageDir, manifest, options = {}) {
|
|
|
86704
86770
|
throw new Error(`the required reviewers of ${packageDir} could not be read (${error instanceof Error ? error.message : String(error)}); fix the reviewer rules and re-run`, { cause: error });
|
|
86705
86771
|
}
|
|
86706
86772
|
if (manifest !== undefined)
|
|
86707
|
-
for (const reviewer of
|
|
86773
|
+
for (const reviewer of await installedRosterRoles(root, manifest))
|
|
86708
86774
|
names.add(reviewer);
|
|
86709
86775
|
if (options.withDrivers === true)
|
|
86710
86776
|
for (const reviewer of (await readDrivers(packageDir, root)).replies.reviewers)
|
|
@@ -86759,7 +86825,7 @@ var init_wave_drivers = __esm(() => {
|
|
|
86759
86825
|
init_driver_read();
|
|
86760
86826
|
init_fs();
|
|
86761
86827
|
init_coverage();
|
|
86762
|
-
|
|
86828
|
+
init_roster();
|
|
86763
86829
|
init_role_switches();
|
|
86764
86830
|
init_estimate();
|
|
86765
86831
|
});
|
|
@@ -88247,7 +88313,7 @@ function buildLedger(input) {
|
|
|
88247
88313
|
run.scopeComplete = scopeComplete;
|
|
88248
88314
|
runs.push(run);
|
|
88249
88315
|
}
|
|
88250
|
-
const fix = manifest === undefined ? { covered: [], gaps: [] } : fixSliceCoverage(manifest, readBy, dispositionsBy, skippedNames);
|
|
88316
|
+
const fix = manifest === undefined ? { covered: [], gaps: [] } : fixSliceCoverage(manifest, readBy, dispositionsBy, skippedNames, input.installed ?? new Set(manifest.slices.flatMap((slice) => slice.roles ?? [])));
|
|
88251
88317
|
const fixCovered = new Set(fix.covered);
|
|
88252
88318
|
gaps.push(...fix.gaps);
|
|
88253
88319
|
const unresolvedRules = selected.filter((r) => input.ruleFiles[r] === undefined);
|
|
@@ -89241,6 +89307,216 @@ var init_readiness = __esm(() => {
|
|
|
89241
89307
|
PARTIAL_LINE = /^Partial review:/;
|
|
89242
89308
|
});
|
|
89243
89309
|
|
|
89310
|
+
// src/lib/live-render.ts
|
|
89311
|
+
function stripAnsi(text) {
|
|
89312
|
+
return text.replace(ANSI_RE, "");
|
|
89313
|
+
}
|
|
89314
|
+
function safeText(text) {
|
|
89315
|
+
return stripAnsi(text.replace(OSC_RE, "")).replace(CONTROL_RE, " ").replace(/ {2,}/g, " ").trim();
|
|
89316
|
+
}
|
|
89317
|
+
function isWideCodePoint(cp) {
|
|
89318
|
+
return cp >= 4352 && cp <= 4447 || cp >= 11904 && cp <= 12350 || cp >= 12353 && cp <= 13311 || cp >= 13312 && cp <= 19903 || cp >= 19968 && cp <= 40959 || cp >= 40960 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 127744 && cp <= 129791 || cp >= 131072 && cp <= 262141;
|
|
89319
|
+
}
|
|
89320
|
+
function displayWidth(text) {
|
|
89321
|
+
const plain = stripAnsi(text);
|
|
89322
|
+
let width = 0;
|
|
89323
|
+
for (const ch of plain) {
|
|
89324
|
+
const cp = ch.codePointAt(0) ?? 0;
|
|
89325
|
+
width += isWideCodePoint(cp) ? 2 : 1;
|
|
89326
|
+
}
|
|
89327
|
+
return width;
|
|
89328
|
+
}
|
|
89329
|
+
function physicalRows(lines, cols) {
|
|
89330
|
+
if (cols <= 0) {
|
|
89331
|
+
return lines.length;
|
|
89332
|
+
}
|
|
89333
|
+
let rows = 0;
|
|
89334
|
+
for (const line of lines) {
|
|
89335
|
+
const width = displayWidth(line);
|
|
89336
|
+
rows += width === 0 ? 1 : Math.ceil(width / cols);
|
|
89337
|
+
}
|
|
89338
|
+
return rows;
|
|
89339
|
+
}
|
|
89340
|
+
function computeRepaint(opts) {
|
|
89341
|
+
const { prevRows, nextLines, cols, sync = false } = opts;
|
|
89342
|
+
let body = "";
|
|
89343
|
+
if (prevRows > 0) {
|
|
89344
|
+
body += "\r";
|
|
89345
|
+
if (prevRows > 1) {
|
|
89346
|
+
body += CURSOR_UP(prevRows - 1);
|
|
89347
|
+
}
|
|
89348
|
+
body += CLEAR_TO_END;
|
|
89349
|
+
}
|
|
89350
|
+
body += nextLines.join(`
|
|
89351
|
+
`);
|
|
89352
|
+
const output = sync ? `${SYNC_BEGIN}${body}${SYNC_END}` : body;
|
|
89353
|
+
return { output, rows: physicalRows(nextLines, cols) };
|
|
89354
|
+
}
|
|
89355
|
+
|
|
89356
|
+
class LiveMarkdownBlock {
|
|
89357
|
+
deps;
|
|
89358
|
+
pending = "";
|
|
89359
|
+
prevRows = 0;
|
|
89360
|
+
prevCols = 0;
|
|
89361
|
+
dirty = false;
|
|
89362
|
+
constructor(deps) {
|
|
89363
|
+
this.deps = deps;
|
|
89364
|
+
}
|
|
89365
|
+
append(text) {
|
|
89366
|
+
if (text.length === 0) {
|
|
89367
|
+
return;
|
|
89368
|
+
}
|
|
89369
|
+
this.pending += text;
|
|
89370
|
+
this.dirty = true;
|
|
89371
|
+
}
|
|
89372
|
+
flush() {
|
|
89373
|
+
if (!this.dirty) {
|
|
89374
|
+
return;
|
|
89375
|
+
}
|
|
89376
|
+
this.repaint();
|
|
89377
|
+
this.dirty = false;
|
|
89378
|
+
}
|
|
89379
|
+
finalize() {
|
|
89380
|
+
if (this.dirty) {
|
|
89381
|
+
this.repaint();
|
|
89382
|
+
this.dirty = false;
|
|
89383
|
+
}
|
|
89384
|
+
if (this.prevRows > 0 || this.pending.length > 0) {
|
|
89385
|
+
this.deps.out(`
|
|
89386
|
+
`);
|
|
89387
|
+
}
|
|
89388
|
+
this.pending = "";
|
|
89389
|
+
this.prevRows = 0;
|
|
89390
|
+
this.prevCols = 0;
|
|
89391
|
+
}
|
|
89392
|
+
repaint() {
|
|
89393
|
+
const cols = this.deps.cols();
|
|
89394
|
+
if (this.prevRows > 0 && cols !== this.prevCols) {
|
|
89395
|
+
this.deps.out(`
|
|
89396
|
+
`);
|
|
89397
|
+
this.prevRows = 0;
|
|
89398
|
+
}
|
|
89399
|
+
const lines = this.deps.render(this.pending).split(`
|
|
89400
|
+
`);
|
|
89401
|
+
const { output, rows } = computeRepaint({ prevRows: this.prevRows, nextLines: lines, cols, sync: this.deps.sync });
|
|
89402
|
+
this.deps.out(output);
|
|
89403
|
+
this.prevRows = rows;
|
|
89404
|
+
this.prevCols = cols;
|
|
89405
|
+
}
|
|
89406
|
+
}
|
|
89407
|
+
var ANSI_RE, OSC_RE, CONTROL_RE, CURSOR_UP = (n) => `\x1B[${n}A`, CLEAR_TO_END = "\x1B[0J", SYNC_BEGIN = "\x1B[?2026h", SYNC_END = "\x1B[?2026l";
|
|
89408
|
+
var init_live_render = __esm(() => {
|
|
89409
|
+
ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
89410
|
+
OSC_RE = /\x1b\][^\x07\x1b]*(?:\x07|\x1b\\)?/g;
|
|
89411
|
+
CONTROL_RE = /[\u0000-\u001f\u007f-\u009f\u061c\u200e\u200f\u2028\u2029\u202a-\u202e\u2066-\u2069]+/g;
|
|
89412
|
+
});
|
|
89413
|
+
|
|
89414
|
+
// src/review/degradation.ts
|
|
89415
|
+
function isDegradationReviewer(reviewer) {
|
|
89416
|
+
return typeof reviewer === "string" && isBlastRadiusScopedFinding({ reviewer }, [DEGRADATION_REVIEWER]);
|
|
89417
|
+
}
|
|
89418
|
+
function hasEvidenceKind(finding) {
|
|
89419
|
+
const kind = finding.evidence_kind;
|
|
89420
|
+
return typeof kind === "string" && REVIEW_EVIDENCE_KINDS.includes(kind);
|
|
89421
|
+
}
|
|
89422
|
+
function omitEvidenceKind(finding) {
|
|
89423
|
+
const { evidence_kind: _dropped, ...rest } = finding;
|
|
89424
|
+
return rest;
|
|
89425
|
+
}
|
|
89426
|
+
function screenDegradationFindings(findings) {
|
|
89427
|
+
const retained = [];
|
|
89428
|
+
const downgraded = [];
|
|
89429
|
+
let screened = 0;
|
|
89430
|
+
for (const finding of findings) {
|
|
89431
|
+
if (!isDegradationReviewer(finding.reviewer)) {
|
|
89432
|
+
retained.push(finding);
|
|
89433
|
+
continue;
|
|
89434
|
+
}
|
|
89435
|
+
screened += 1;
|
|
89436
|
+
const outOfList = finding.evidence_kind !== undefined && !hasEvidenceKind(finding);
|
|
89437
|
+
const clean = outOfList ? omitEvidenceKind(finding) : finding;
|
|
89438
|
+
const severity = clean.severity;
|
|
89439
|
+
if (severity === undefined || !SCREENED_SEVERITIES.includes(severity) || hasEvidenceKind(clean)) {
|
|
89440
|
+
retained.push(clean);
|
|
89441
|
+
continue;
|
|
89442
|
+
}
|
|
89443
|
+
const from = severity;
|
|
89444
|
+
const marker = { rule: DEGRADATION_NO_EVIDENCE_RULE, original_severity: from };
|
|
89445
|
+
const lowered = { ...clean, severity: "info", evidence_screen: marker };
|
|
89446
|
+
if (lowered.blocking_merge === true) {
|
|
89447
|
+
lowered.blocking_merge = false;
|
|
89448
|
+
}
|
|
89449
|
+
retained.push(lowered);
|
|
89450
|
+
downgraded.push({
|
|
89451
|
+
finding: lowered,
|
|
89452
|
+
rule: DEGRADATION_NO_EVIDENCE_RULE,
|
|
89453
|
+
from,
|
|
89454
|
+
to: "info",
|
|
89455
|
+
detail: `${from} without evidence_kind (one of ${REVIEW_EVIDENCE_KINDS.join(", ")}) is unproven; kept as info`
|
|
89456
|
+
});
|
|
89457
|
+
}
|
|
89458
|
+
return { retained, downgraded, screened };
|
|
89459
|
+
}
|
|
89460
|
+
function degradationScreenRecord(result) {
|
|
89461
|
+
return {
|
|
89462
|
+
screened: result.screened,
|
|
89463
|
+
downgraded: result.downgraded.map((entry) => ({
|
|
89464
|
+
id: entry.finding.id ?? "(no id)",
|
|
89465
|
+
reviewer: entry.finding.reviewer ?? "(unknown)",
|
|
89466
|
+
from: entry.from,
|
|
89467
|
+
to: entry.to,
|
|
89468
|
+
rule: entry.rule,
|
|
89469
|
+
detail: entry.detail
|
|
89470
|
+
}))
|
|
89471
|
+
};
|
|
89472
|
+
}
|
|
89473
|
+
function escapeCell(value) {
|
|
89474
|
+
return safeText(value).replace(/[\\|`]/g, (ch) => `\\${ch}`).replace(/\[/g, "\\[").replace(/</g, "<");
|
|
89475
|
+
}
|
|
89476
|
+
function renderDegradationScreenMarkdown(record) {
|
|
89477
|
+
const lines = [
|
|
89478
|
+
"## Degradation evidence screen",
|
|
89479
|
+
"",
|
|
89480
|
+
`rule: ${DEGRADATION_NO_EVIDENCE_RULE}`,
|
|
89481
|
+
`screened: ${record.screened}`,
|
|
89482
|
+
`downgraded: ${record.downgraded.length}`,
|
|
89483
|
+
""
|
|
89484
|
+
];
|
|
89485
|
+
if (record.downgraded.length === 0) {
|
|
89486
|
+
lines.push(record.screened === 0 ? "_no `review-degradation` finding in this round._" : "_every `major` or `blocker` from `review-degradation` carried an evidence kind._");
|
|
89487
|
+
lines.push("");
|
|
89488
|
+
return lines.join(`
|
|
89489
|
+
`);
|
|
89490
|
+
}
|
|
89491
|
+
lines.push("| finding | reviewer | rule | from | to | why |");
|
|
89492
|
+
lines.push("|---|---|---|---|---|---|");
|
|
89493
|
+
for (const entry of record.downgraded) {
|
|
89494
|
+
lines.push(`| ${escapeCell(entry.id)} | ${escapeCell(entry.reviewer)} | ${entry.rule} | ${entry.from} | ${entry.to} | ${escapeCell(entry.detail)} |`);
|
|
89495
|
+
}
|
|
89496
|
+
lines.push("");
|
|
89497
|
+
lines.push("Downgraded findings are kept, not deleted, and marked unproven: add an evidence kind to restore the severity.");
|
|
89498
|
+
lines.push("");
|
|
89499
|
+
return lines.join(`
|
|
89500
|
+
`);
|
|
89501
|
+
}
|
|
89502
|
+
function renderDegradationScreenLines(record) {
|
|
89503
|
+
if (record.screened === 0) {
|
|
89504
|
+
return [];
|
|
89505
|
+
}
|
|
89506
|
+
const lines = [`degradation screen: screened=${record.screened} downgraded=${record.downgraded.length}`];
|
|
89507
|
+
for (const entry of record.downgraded) {
|
|
89508
|
+
lines.push(` downgraded ${safeText(entry.id)} ${entry.from} -> ${entry.to} [${entry.rule}]: ${safeText(entry.detail)}`);
|
|
89509
|
+
}
|
|
89510
|
+
return lines;
|
|
89511
|
+
}
|
|
89512
|
+
var DEGRADATION_REVIEWER = "review-degradation", DEGRADATION_NO_EVIDENCE_RULE = "degradation-no-evidence", SCREENED_SEVERITIES;
|
|
89513
|
+
var init_degradation = __esm(() => {
|
|
89514
|
+
init_live_render();
|
|
89515
|
+
init_blast_radius();
|
|
89516
|
+
init_types4();
|
|
89517
|
+
SCREENED_SEVERITIES = ["blocker", "major"];
|
|
89518
|
+
});
|
|
89519
|
+
|
|
89244
89520
|
// src/review/caps.ts
|
|
89245
89521
|
function emptySeverityHistogram() {
|
|
89246
89522
|
return { blocker: 0, major: 0, minor: 0, info: 0 };
|
|
@@ -89478,6 +89754,7 @@ var init_caps = __esm(() => {
|
|
|
89478
89754
|
"review-architecture",
|
|
89479
89755
|
"review-highload",
|
|
89480
89756
|
"review-regression",
|
|
89757
|
+
"review-degradation",
|
|
89481
89758
|
"review-core-boundaries",
|
|
89482
89759
|
"code-boss-reviewer",
|
|
89483
89760
|
"code-ai-review"
|
|
@@ -90061,6 +90338,17 @@ function mergeCluster(members) {
|
|
|
90061
90338
|
finding.class_scope = { ...base, sites };
|
|
90062
90339
|
}
|
|
90063
90340
|
}
|
|
90341
|
+
const carriedClass = canonical.degradation_class ?? others.find((member) => member.degradation_class !== undefined)?.degradation_class;
|
|
90342
|
+
if (carriedClass !== undefined) {
|
|
90343
|
+
finding.degradation_class = carriedClass;
|
|
90344
|
+
}
|
|
90345
|
+
let carriedKind = canonical.evidence_kind;
|
|
90346
|
+
if (carriedKind === undefined && canonical.evidence_screen === undefined) {
|
|
90347
|
+
carriedKind = others.find((member) => member.evidence_kind !== undefined)?.evidence_kind;
|
|
90348
|
+
}
|
|
90349
|
+
if (carriedKind !== undefined) {
|
|
90350
|
+
finding.evidence_kind = carriedKind;
|
|
90351
|
+
}
|
|
90064
90352
|
const higher = severityRule === "max" ? [] : others.filter((member) => !decides(member) && rank(member.severity) > rank(canonical.severity));
|
|
90065
90353
|
const top = higher.length === 0 ? undefined : bestOf(higher);
|
|
90066
90354
|
return {
|
|
@@ -90547,7 +90835,9 @@ async function createManagedReviewPackage(input) {
|
|
|
90547
90835
|
const { result: verification, reclaimed: externalReclaims } = applyExternalVerdictRule(merged);
|
|
90548
90836
|
const scoped = partitionExternalFindings(verification.retained);
|
|
90549
90837
|
const scopeB = await screenScopeBFindings(input, packageDir, scoped.internal, passKeys);
|
|
90550
|
-
const
|
|
90838
|
+
const degradation = screenDegradationFindings(scopeB.retained);
|
|
90839
|
+
const degradationScreen = degradationScreenRecord(degradation);
|
|
90840
|
+
const deduped = mergeDuplicates(degradation.retained);
|
|
90551
90841
|
const premiseSet = [...deduped.findings, ...scoped.external];
|
|
90552
90842
|
const carriedHolds = premiseClaims === undefined ? carriedPremiseHolds(premiseSet, await readPreviousFindings(packageDir)) : undefined;
|
|
90553
90843
|
if (carriedHolds?.ok === false)
|
|
@@ -90640,7 +90930,7 @@ async function createManagedReviewPackage(input) {
|
|
|
90640
90930
|
findings,
|
|
90641
90931
|
...carriedHolds?.ok === true && carriedHolds.carried > 0 ? { carried: { holds: carriedHolds.carried, judges: carriedHolds.judges } } : {}
|
|
90642
90932
|
});
|
|
90643
|
-
await writeFileAtomic(path219.join(packageDir, "scope.md"), renderScope(input, flowMatch, at, verification, carried, caps, { externalReclaims, scopeBScreen: scopeB.record, filterStats, allFindings: findings }) + premiseBlock + renderDuplicatesMarkdown(deduped.merges) + renderChunkPassMarkdown(chunkPassIngest));
|
|
90933
|
+
await writeFileAtomic(path219.join(packageDir, "scope.md"), renderScope(input, flowMatch, at, verification, carried, caps, { externalReclaims, scopeBScreen: scopeB.record, degradationScreen, filterStats, allFindings: findings }) + premiseBlock + renderDuplicatesMarkdown(deduped.merges) + renderChunkPassMarkdown(chunkPassIngest));
|
|
90644
90934
|
await writeFileAtomic(path219.join(packageDir, "coverage.md"), renderCoverage(coverage));
|
|
90645
90935
|
await writeFileAtomic(path219.join(packageDir, "report.md"), withSpendSection(renderReport2(report, input.mode), input.research));
|
|
90646
90936
|
await withOperatorDecisionsLock(packageDir, () => writeFileAtomic(path219.join(packageDir, "findings.json"), `${JSON.stringify(findings.map(toContractFinding), null, 2)}
|
|
@@ -90672,6 +90962,7 @@ async function createManagedReviewPackage(input) {
|
|
|
90672
90962
|
filterStats,
|
|
90673
90963
|
reviewNotes,
|
|
90674
90964
|
scopeBScreen: scopeB.record,
|
|
90965
|
+
degradationScreen,
|
|
90675
90966
|
duplicates: deduped.merges,
|
|
90676
90967
|
chunkPass: chunkPassIngest
|
|
90677
90968
|
};
|
|
@@ -91327,6 +91618,15 @@ function toContractFinding(finding) {
|
|
|
91327
91618
|
if (finding.class_scope !== undefined) {
|
|
91328
91619
|
record.class_scope = finding.class_scope;
|
|
91329
91620
|
}
|
|
91621
|
+
if (finding.degradation_class !== undefined) {
|
|
91622
|
+
record.degradation_class = finding.degradation_class;
|
|
91623
|
+
}
|
|
91624
|
+
if (finding.evidence_kind !== undefined) {
|
|
91625
|
+
record.evidence_kind = finding.evidence_kind;
|
|
91626
|
+
}
|
|
91627
|
+
if (finding.evidence_screen !== undefined) {
|
|
91628
|
+
record.evidence_screen = finding.evidence_screen;
|
|
91629
|
+
}
|
|
91330
91630
|
if (finding.global_id !== undefined) {
|
|
91331
91631
|
record.global_id = finding.global_id;
|
|
91332
91632
|
}
|
|
@@ -91585,10 +91885,24 @@ function parseLegacyReport(report, reviewers) {
|
|
|
91585
91885
|
if (location.line !== undefined) {
|
|
91586
91886
|
finding.line = location.line;
|
|
91587
91887
|
}
|
|
91888
|
+
if (finding.reviewer === "review-degradation") {
|
|
91889
|
+
const degradationClass = closedListValue(block, ["class", "degradation class", "degradation_class"], REVIEW_DEGRADATION_CLASSES);
|
|
91890
|
+
if (degradationClass !== undefined) {
|
|
91891
|
+
finding.degradation_class = degradationClass;
|
|
91892
|
+
}
|
|
91893
|
+
const evidenceKind = closedListValue(block, ["evidence kind", "evidence_kind"], REVIEW_EVIDENCE_KINDS);
|
|
91894
|
+
if (evidenceKind !== undefined) {
|
|
91895
|
+
finding.evidence_kind = evidenceKind;
|
|
91896
|
+
}
|
|
91897
|
+
}
|
|
91588
91898
|
findings.push(finding);
|
|
91589
91899
|
}
|
|
91590
91900
|
return findings;
|
|
91591
91901
|
}
|
|
91902
|
+
function closedListValue(block, labels, allowed) {
|
|
91903
|
+
const value = labelledField(block, labels)?.trim().replace(/^[*_`]+|[*_`]+$/g, "").trim().toLowerCase();
|
|
91904
|
+
return allowed.find((candidate) => candidate === value);
|
|
91905
|
+
}
|
|
91592
91906
|
function labelledField(block, labels) {
|
|
91593
91907
|
const lines = block.split(`
|
|
91594
91908
|
`);
|
|
@@ -91770,7 +92084,13 @@ async function readPreFilterScopeBlock(packageDir) {
|
|
|
91770
92084
|
return extractPreFilterScopeBlock(await readFile114(scopePath, "utf8")) ?? undefined;
|
|
91771
92085
|
}
|
|
91772
92086
|
function renderScope(input, flowMatch, at, verification, carriedPreFilter, caps, extras = {}) {
|
|
91773
|
-
const {
|
|
92087
|
+
const {
|
|
92088
|
+
externalReclaims = [],
|
|
92089
|
+
scopeBScreen = { source: "none", scopeBFindings: 0 },
|
|
92090
|
+
degradationScreen = { screened: 0, downgraded: [] },
|
|
92091
|
+
filterStats,
|
|
92092
|
+
allFindings = []
|
|
92093
|
+
} = extras;
|
|
91774
92094
|
const preFilter = input.scope?.counts ?? input.scopeCounts;
|
|
91775
92095
|
const drops = input.scope?.drops;
|
|
91776
92096
|
const head = `# Review Scope
|
|
@@ -91792,7 +92112,8 @@ ${renderStageCountsMarkdown({
|
|
|
91792
92112
|
})}
|
|
91793
92113
|
|
|
91794
92114
|
${renderCapsMarkdown(caps)}
|
|
91795
|
-
${renderScopeBScreenMarkdown(scopeBScreen)}
|
|
92115
|
+
${renderScopeBScreenMarkdown(scopeBScreen)}
|
|
92116
|
+
${renderDegradationScreenMarkdown(degradationScreen)}${filterStats === undefined ? "" : `
|
|
91796
92117
|
${renderFilterStatsMarkdown(filterStats)}`}${renderExternalReclaimsMarkdown(externalReclaims)}${renderPathMissingMarkdown(allFindings)}${renderQuoteUnlocatedMarkdown(allFindings)}`;
|
|
91797
92118
|
return carriedPreFilter === undefined ? head : `${head.trimEnd()}
|
|
91798
92119
|
|
|
@@ -92030,6 +92351,7 @@ var init_managed = __esm(() => {
|
|
|
92030
92351
|
init_verification();
|
|
92031
92352
|
init_pr_comments();
|
|
92032
92353
|
init_blast_radius();
|
|
92354
|
+
init_degradation();
|
|
92033
92355
|
init_caps();
|
|
92034
92356
|
init_filter_stats();
|
|
92035
92357
|
init_dedupe();
|
|
@@ -92648,6 +92970,7 @@ var init_completion_gate = __esm(() => {
|
|
|
92648
92970
|
"--no-prior-round",
|
|
92649
92971
|
"--detail",
|
|
92650
92972
|
"--brief",
|
|
92973
|
+
"--degradation",
|
|
92651
92974
|
"--all",
|
|
92652
92975
|
"--gating",
|
|
92653
92976
|
"--style",
|
|
@@ -116856,14 +117179,14 @@ var init_registry2 = __esm(() => {
|
|
|
116856
117179
|
label: "Codex",
|
|
116857
117180
|
binary: "codex",
|
|
116858
117181
|
detect: ["--version"],
|
|
116859
|
-
versionPattern: "codex-cli (\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z
|
|
117182
|
+
versionPattern: "^codex-cli (\\d+\\.\\d+\\.\\d+(?:-[0-9A-Za-z]+(?:[.-][0-9A-Za-z]+)*)?)[ \\t\\r\\n]*$(?![\\s\\S])",
|
|
116860
117183
|
knownGoodRange: { min: "0.147.0" },
|
|
116861
117184
|
sandboxModes: ["read-only", "worktree-write"],
|
|
116862
117185
|
streamingInput: false,
|
|
116863
117186
|
resumable: true,
|
|
116864
117187
|
reportsCost: false,
|
|
116865
117188
|
budgetFlag: false,
|
|
116866
|
-
notes: "Resume requires NOT passing `--ephemeral`: an ephemeral thread fails resume with " + "`no rollout found for thread id \u2026 (code -32600)`. `codex exec resume` also takes a " + "narrower flag set than `codex exec` \u2014 no `-s`, no `-C` \u2014 so a read-only resume inherits " + "its sandbox, a write run re-asserts its confinement as `-c` overrides, and it must be " + "spawned with cwd already set to the worktree. Write mode needs codex 0.159.2 up to, but not including, 0.160.0."
|
|
117189
|
+
notes: "Resume requires NOT passing `--ephemeral`: an ephemeral thread fails resume with " + "`no rollout found for thread id \u2026 (code -32600)`. `codex exec resume` also takes a " + "narrower flag set than `codex exec` \u2014 no `-s`, no `-C` \u2014 so a read-only resume inherits " + "its sandbox, a write run re-asserts its confinement as `-c` overrides, and it must be " + "spawned with cwd already set to the worktree. Write mode needs codex 0.159.2 up to, but not including, 0.160.0, or exactly 0.164.0."
|
|
116867
117190
|
},
|
|
116868
117191
|
{
|
|
116869
117192
|
id: "claude-cli",
|
|
@@ -116924,11 +117247,14 @@ function codecWriteVersionRefusal(entry, version) {
|
|
|
116924
117247
|
if (verified === undefined)
|
|
116925
117248
|
return;
|
|
116926
117249
|
const range = `\`${entry.binary}\` ${verified.min} up to, but not including, ${verified.before}`;
|
|
116927
|
-
const
|
|
117250
|
+
const exact = verified.exact?.length ? `, or exactly ${verified.exact.join(", ")}` : "";
|
|
117251
|
+
const prefix = `${entry.label} write mode needs ${range}${exact}, the versions whose sandbox confinement was measured; `;
|
|
116928
117252
|
if (version === undefined)
|
|
116929
117253
|
return `${prefix}the installed version could not be read`;
|
|
116930
117254
|
if (!/^\d+\.\d+\.\d+$/.test(version))
|
|
116931
117255
|
return `${prefix}found ${version}, a pre-release`;
|
|
117256
|
+
if (verified.exact?.includes(version))
|
|
117257
|
+
return;
|
|
116932
117258
|
if (compareVersions2(version, verified.min) < 0)
|
|
116933
117259
|
return `${prefix}found ${version}, which is older`;
|
|
116934
117260
|
if (compareVersions2(version, verified.before) >= 0) {
|
|
@@ -117015,7 +117341,7 @@ var init_dispatch2 = __esm(() => {
|
|
|
117015
117341
|
IMPLEMENTED_ACP_SANDBOX_MODES = ["read-only", "worktree-write"];
|
|
117016
117342
|
CODEC_WRITE_AGENT_IDS = ["claude-cli", "codex-cli"];
|
|
117017
117343
|
CODEC_WRITE_VERIFIED_VERSIONS = {
|
|
117018
|
-
"codex-cli": { min: "0.159.2", before: "0.160.0" }
|
|
117344
|
+
"codex-cli": { min: "0.159.2", before: "0.160.0", exact: ["0.164.0"] }
|
|
117019
117345
|
};
|
|
117020
117346
|
});
|
|
117021
117347
|
|
|
@@ -135660,7 +135986,8 @@ One schema per row: ${DISPOSITION_SCHEMA}`);
|
|
|
135660
135986
|
const depth = recordedDepth?.depth;
|
|
135661
135987
|
const required = await requiredReviewers(root, mode, files, { blastRadius, prContext: packageHasPrContext(packageDir), depth, enabledRoles: await packageEnabledRoles(packageDir) });
|
|
135662
135988
|
const { payloadSlices, payloadDispatchCounts, ruleFiles, toolLogs: toolLogPaths, models, assignments, replies, dropped } = await readDrivers(inputDir, root);
|
|
135663
|
-
const
|
|
135989
|
+
const installed = await installedReviewerNames(root);
|
|
135990
|
+
const selected = [...new Set([...required.required, ...manifest === undefined ? [] : manifestRosterRoles(manifest, installed)])].sort();
|
|
135664
135991
|
const skipped = parseSkips(rest).map((skip) => selected.includes(skip.reviewer) ? skip : { ...skip, reason: `${skip.reason} (not required this round)` });
|
|
135665
135992
|
const decided = await skipDecision(packageDir, skipped.filter((skip) => selected.includes(skip.reviewer)).map((skip) => skip.reviewer));
|
|
135666
135993
|
if (!decided.ok)
|
|
@@ -135706,6 +136033,7 @@ One schema per row: ${DISPOSITION_SCHEMA}`);
|
|
|
135706
136033
|
manifestPath: path315.relative(process.cwd(), manifestPath),
|
|
135707
136034
|
mutationRecorded: await packageHasMutationRecord(packageDir),
|
|
135708
136035
|
skipped,
|
|
136036
|
+
installed,
|
|
135709
136037
|
superseded: required.excluded.filter((entry) => entry.reason.startsWith("superseded by ") || entry.reason.startsWith("overlap group ")),
|
|
135710
136038
|
supersedeNotes: required.notes,
|
|
135711
136039
|
screened,
|
|
@@ -146893,6 +147221,94 @@ var init_provider_default = __esm(() => {
|
|
|
146893
147221
|
init_providers();
|
|
146894
147222
|
});
|
|
146895
147223
|
|
|
147224
|
+
// src/review/degradation-payload.ts
|
|
147225
|
+
function buildDegradationConsumers(input) {
|
|
147226
|
+
const cap = input.cap === undefined || !Number.isFinite(input.cap) ? DEFAULT_DEGRADATION_CONSUMER_CAP : Math.max(0, Math.trunc(input.cap));
|
|
147227
|
+
const inDiff = new Set([...input.changedFiles ?? [], ...input.changedExports.map((entry) => entry.file)]);
|
|
147228
|
+
const nodePaths = new Set(input.graph.nodes.map((node) => node.path));
|
|
147229
|
+
const best = new Map;
|
|
147230
|
+
const unresolved = [];
|
|
147231
|
+
const barrels = new Set(input.barrels ?? []);
|
|
147232
|
+
const targets = [...new Set([...input.changedExports.map((entry) => entry.file), ...barrels])];
|
|
147233
|
+
for (const target of targets) {
|
|
147234
|
+
if (!nodePaths.has(target)) {
|
|
147235
|
+
unresolved.push(target);
|
|
147236
|
+
continue;
|
|
147237
|
+
}
|
|
147238
|
+
const result = computeAffected(input.graph, target, { depth: 1, ranked: true });
|
|
147239
|
+
for (const dependent of result.ranked) {
|
|
147240
|
+
if (inDiff.has(dependent.path) || barrels.has(dependent.path) || best.has(dependent.path)) {
|
|
147241
|
+
continue;
|
|
147242
|
+
}
|
|
147243
|
+
best.set(dependent.path, { file: dependent.path, via: target, fanIn: dependent.fanIn });
|
|
147244
|
+
}
|
|
147245
|
+
}
|
|
147246
|
+
const ranked = [...best.values()].sort((a, b) => a.fanIn !== b.fanIn ? b.fanIn - a.fanIn : a.file < b.file ? -1 : a.file > b.file ? 1 : 0);
|
|
147247
|
+
const consumers = ranked.slice(0, cap);
|
|
147248
|
+
const dropped = ranked.slice(cap);
|
|
147249
|
+
const capped = dropped.length > 0;
|
|
147250
|
+
return {
|
|
147251
|
+
consumers,
|
|
147252
|
+
capped,
|
|
147253
|
+
dropped,
|
|
147254
|
+
cap,
|
|
147255
|
+
total: ranked.length,
|
|
147256
|
+
changedExports: input.changedExports.map((entry) => ({ file: entry.file, names: entry.names })),
|
|
147257
|
+
unresolved: [...new Set(unresolved)].sort(),
|
|
147258
|
+
summary: capped ? `the cap of ${cap} dropped ${dropped.length} of ${ranked.length} consumers; the first ${NOT_TRACED_LISTED} dropped ones are listed, the rest are counted, and none were traced` : `the cap of ${cap} dropped no consumer (${ranked.length} found)`
|
|
147259
|
+
};
|
|
147260
|
+
}
|
|
147261
|
+
function changedExportsOf(radius) {
|
|
147262
|
+
const seeds = radius.reach?.seeds;
|
|
147263
|
+
if (radius.reach?.mode === "symbol" && seeds !== undefined) {
|
|
147264
|
+
return seeds.filter((seed) => seed.names === "*" || seed.names.length > 0).map((seed) => ({ file: seed.file, names: seed.names === "*" ? "*" : [...seed.names].sort() })).sort((a, b) => a.file < b.file ? -1 : a.file > b.file ? 1 : 0);
|
|
147265
|
+
}
|
|
147266
|
+
const unresolved = new Set(radius.unresolved.map((entry) => entry.file));
|
|
147267
|
+
return radius.changedFiles.filter((file) => !unresolved.has(file)).map((file) => ({ file, names: "*" }));
|
|
147268
|
+
}
|
|
147269
|
+
function barrelsOf(radius) {
|
|
147270
|
+
return radius.reach?.mode === "symbol" ? [...new Set(radius.reach.barrels)].sort() : [];
|
|
147271
|
+
}
|
|
147272
|
+
function renderDegradationPayloadBrief(payload) {
|
|
147273
|
+
const lines = [];
|
|
147274
|
+
lines.push("SCOPE: degradation");
|
|
147275
|
+
lines.push("");
|
|
147276
|
+
lines.push("QUESTION: after this change, is the code worse than on the base branch: in behaviour, in defaults, in speed or in");
|
|
147277
|
+
lines.push("structure, and can that be shown? Scope is the diff plus the consumers below. The consumer list is the code");
|
|
147278
|
+
lines.push("graph's, not yours: trace those, and say so when you traced fewer.");
|
|
147279
|
+
lines.push("");
|
|
147280
|
+
lines.push(`CHANGED SHARED EXPORTS (${payload.changedExports.length}):`);
|
|
147281
|
+
for (const entry of payload.changedExports) {
|
|
147282
|
+
lines.push(` - ${safe(entry.file)} ${entry.names === "*" ? "(every export)" : entry.names.map(safe).join(", ")}`);
|
|
147283
|
+
}
|
|
147284
|
+
lines.push("");
|
|
147285
|
+
lines.push(`CONSUMERS (${payload.consumers.length} of ${payload.total}; the files that import the module holding each changed export, or a barrel re-exporting it; most imported first, cap ${payload.cap}):`);
|
|
147286
|
+
for (const consumer of payload.consumers) {
|
|
147287
|
+
lines.push(` - ${safe(consumer.file)} imports ${safe(consumer.via)} [fan-in ${consumer.fanIn}]`);
|
|
147288
|
+
}
|
|
147289
|
+
lines.push("");
|
|
147290
|
+
lines.push(`CAP: ${payload.summary}. The cap is one budget shared by all changed exports, not one per export.`);
|
|
147291
|
+
if (payload.dropped.length > 0) {
|
|
147292
|
+
const listed = payload.dropped.slice(0, NOT_TRACED_LISTED).map((consumer) => safe(consumer.file));
|
|
147293
|
+
const more = payload.dropped.length - listed.length;
|
|
147294
|
+
lines.push(`NOT TRACED (${payload.dropped.length}): ${listed.join(", ")}${more > 0 ? `, and ${more} more` : ""}`);
|
|
147295
|
+
}
|
|
147296
|
+
if (payload.unresolved.length > 0) {
|
|
147297
|
+
lines.push(`NOT ANSWERABLE (${payload.unresolved.length}): ${payload.unresolved.map(safe).join(", ")} absent from the code graph. Their consumers are unknown, not none.`);
|
|
147298
|
+
}
|
|
147299
|
+
lines.push("");
|
|
147300
|
+
return lines.join(`
|
|
147301
|
+
`);
|
|
147302
|
+
}
|
|
147303
|
+
function safe(text) {
|
|
147304
|
+
return text.replace(CONTROL_CHARACTERS2, (char) => `\\u${char.charCodeAt(0).toString(16).padStart(4, "0")}`);
|
|
147305
|
+
}
|
|
147306
|
+
var DEFAULT_DEGRADATION_CONSUMER_CAP = 40, NOT_TRACED_LISTED = 20, CONTROL_CHARACTERS2;
|
|
147307
|
+
var init_degradation_payload = __esm(() => {
|
|
147308
|
+
init_affected();
|
|
147309
|
+
CONTROL_CHARACTERS2 = new RegExp("[\\u0000-\\u001f\\u007f-\\u009f\\u061c\\u200e\\u200f\\u2028\\u2029\\u202a-\\u202e\\u2066-\\u2069]", "g");
|
|
147310
|
+
});
|
|
147311
|
+
|
|
146896
147312
|
// src/harness/routing/tier-rank-agent.ts
|
|
146897
147313
|
import path340 from "path";
|
|
146898
147314
|
function tierRankCacheFilePath(dir) {
|
|
@@ -148412,6 +148828,9 @@ async function runCreate(mode, args, createSeams = {}) {
|
|
|
148412
148828
|
console.log(` exempted ${exemption.finding.id ?? "<unidentified>"} [${exemption.rule} not applied]: ${exemption.detail}`);
|
|
148413
148829
|
}
|
|
148414
148830
|
}
|
|
148831
|
+
for (const line of renderDegradationScreenLines(result.degradationScreen)) {
|
|
148832
|
+
console.log(line);
|
|
148833
|
+
}
|
|
148415
148834
|
const chunkPass = result.chunkPass;
|
|
148416
148835
|
if (chunkPass.brought.length > 0 || chunkPass.alreadyReported.length > 0) {
|
|
148417
148836
|
console.log(`chunk-pass findings: brought_in=${chunkPass.brought.length} already_in_report=${chunkPass.alreadyReported.length}`);
|
|
@@ -149610,7 +150029,7 @@ async function runRequired(args) {
|
|
|
149610
150029
|
depth: decision.depth
|
|
149611
150030
|
});
|
|
149612
150031
|
const sliceManifest = packageDir === undefined ? undefined : await readJsonFileOr(path343.join(packageDir, "slices", "manifest.json"), undefined);
|
|
149613
|
-
const fixRoles = sliceManifest !== undefined && Array.isArray(sliceManifest.slices) ?
|
|
150032
|
+
const fixRoles = sliceManifest !== undefined && Array.isArray(sliceManifest.slices) ? (await installedRosterRoles(process.cwd(), sliceManifest)).filter((role) => !baseSet.required.includes(role)) : [];
|
|
149614
150033
|
const set = fixRoles.length === 0 ? baseSet : { ...baseSet, required: [...baseSet.required, ...fixRoles].sort() };
|
|
149615
150034
|
if (packageDir !== undefined) {
|
|
149616
150035
|
const basis = await packageDepthBasis(packageDir);
|
|
@@ -150018,6 +150437,15 @@ async function runBlastRadius(args) {
|
|
|
150018
150437
|
const maxFiles = parseNonNegativeInteger(optionValue(args, "--max-files"), "--max-files");
|
|
150019
150438
|
const includeRelatedTests = !args.includes("--no-related-tests");
|
|
150020
150439
|
const isFinalRound = args.includes("--final");
|
|
150440
|
+
const wantsDegradation = args.includes("--degradation");
|
|
150441
|
+
if (wantsDegradation) {
|
|
150442
|
+
const clash = ["--json", "--brief", "--out"].find((flag) => flag === "--out" ? optionValue(args, flag) !== undefined : args.includes(flag));
|
|
150443
|
+
if (clash !== undefined)
|
|
150444
|
+
throw new Error(`--degradation prints the review-degradation payload to stdout and cannot be combined with ${clash}; run blast-radius again for that output.`);
|
|
150445
|
+
} else if (args.some((arg) => arg === "--degradation-cap")) {
|
|
150446
|
+
throw new Error("--degradation-cap only applies together with --degradation.");
|
|
150447
|
+
}
|
|
150448
|
+
const degradationCap = wantsDegradation ? parseNonNegativeInteger(optionValue(args, "--degradation-cap"), "--degradation-cap") : undefined;
|
|
150021
150449
|
const explicit = optionValue(args, "--changed");
|
|
150022
150450
|
const ref = optionValue(args, "--ref") ?? optionValue(args, "--base");
|
|
150023
150451
|
const reachMode = optionValue(args, "--reach") ?? "symbol";
|
|
@@ -150048,8 +150476,12 @@ async function runBlastRadius(args) {
|
|
|
150048
150476
|
depth,
|
|
150049
150477
|
maxFiles
|
|
150050
150478
|
});
|
|
150051
|
-
|
|
150052
|
-
|
|
150479
|
+
const reuse = !decision.recompute && previous !== undefined;
|
|
150480
|
+
const recomputeForDegradation = reuse && wantsDegradation;
|
|
150481
|
+
const recompute = decision.recompute || recomputeForDegradation;
|
|
150482
|
+
const recomputeReason = recomputeForDegradation ? "the degradation payload needs the graph" : decision.reason;
|
|
150483
|
+
console.error(`recompute: ${recompute ? "yes" : "no"} \u2014 ${recomputeReason}`);
|
|
150484
|
+
if (reuse && !wantsDegradation) {
|
|
150053
150485
|
await emitBlastRadius(previous, args);
|
|
150054
150486
|
return;
|
|
150055
150487
|
}
|
|
@@ -150092,7 +150524,14 @@ async function runBlastRadius(args) {
|
|
|
150092
150524
|
textLinks,
|
|
150093
150525
|
testCommand: detectTestCommand(projectRoot)
|
|
150094
150526
|
});
|
|
150095
|
-
|
|
150527
|
+
const degradationBrief = wantsDegradation ? renderDegradationPayloadBrief(buildDegradationConsumers({
|
|
150528
|
+
graph,
|
|
150529
|
+
changedExports: changedExportsOf(radius),
|
|
150530
|
+
changedFiles: radius.changedFiles,
|
|
150531
|
+
barrels: barrelsOf(radius),
|
|
150532
|
+
cap: degradationCap
|
|
150533
|
+
})) : undefined;
|
|
150534
|
+
await emitBlastRadius(radius, args, degradationBrief);
|
|
150096
150535
|
const reasoned = (radius.counts.droppedUnrelatedSymbol ?? 0) + (radius.counts.droppedHubDamped ?? 0) + (radius.counts.droppedTextHub ?? 0) + (radius.counts.droppedTestConsumer ?? 0);
|
|
150097
150536
|
if (reasoned > 0) {
|
|
150098
150537
|
console.error(`omitted with a reason: ${radius.counts.droppedUnrelatedSymbol ?? 0} unrelated-symbol, ${radius.counts.droppedHubDamped ?? 0} hub-damped, ${radius.counts.droppedTextHub ?? 0} text-hub, ${radius.counts.droppedTestConsumer ?? 0} test-consumer. ` + "They are context, not owed a chunk pass.");
|
|
@@ -150106,7 +150545,7 @@ async function runBlastRadius(args) {
|
|
|
150106
150545
|
}
|
|
150107
150546
|
if (radius.counts.droppedByCap > 0) {
|
|
150108
150547
|
console.error(`cap: ${radius.counts.droppedByCap} of ${radius.counts.candidates} candidate files were NOT reviewed (blast_radius_max_files=${radius.maxFiles}). They are listed in the record. ` + `Cut blast radius: run the waves \u2014 keryx review chunks plan --package ${packageOption ?? "<review>"}`);
|
|
150109
|
-
if (packageOption !== undefined) {
|
|
150548
|
+
if (packageOption !== undefined && !wantsDegradation) {
|
|
150110
150549
|
const planned = await recordRadiusAndPlan(resolvePackageDir(args, cwd), resolveProjectRoot2(cwd), radius, packageOption);
|
|
150111
150550
|
console.error(planned);
|
|
150112
150551
|
}
|
|
@@ -150115,8 +150554,10 @@ async function runBlastRadius(args) {
|
|
|
150115
150554
|
console.error(`unresolved: ${radius.counts.changedFilesUnresolved} changed file(s) are absent from the code graph; their blast radius is unknown, not empty.`);
|
|
150116
150555
|
}
|
|
150117
150556
|
}
|
|
150118
|
-
async function emitBlastRadius(radius, args) {
|
|
150119
|
-
if (
|
|
150557
|
+
async function emitBlastRadius(radius, args, degradationBrief) {
|
|
150558
|
+
if (degradationBrief !== undefined) {
|
|
150559
|
+
console.log(degradationBrief);
|
|
150560
|
+
} else if (args.includes("--json")) {
|
|
150120
150561
|
console.log(JSON.stringify(radius, null, 2));
|
|
150121
150562
|
} else if (args.includes("--brief")) {
|
|
150122
150563
|
console.log(renderBlastRadiusDispatchBrief(radius));
|
|
@@ -150673,14 +151114,19 @@ Writes mutation-<n>.json and .md to research-evidence/.`);
|
|
|
150673
151114
|
function printBlastRadiusHelp() {
|
|
150674
151115
|
console.log(`keryx review blast-radius [--ref <base> | --changed a,b] [--base <ref>] [--depth <n>] [--max-files <n>]
|
|
150675
151116
|
[--no-related-tests] [--final] [--previous <blast-radius.json>]
|
|
150676
|
-
[--package <review-dir>] [--reach symbol|file] [--no-text-links] [--json | --brief] [--out <file>]
|
|
151117
|
+
[--package <review-dir>] [--reach symbol|file] [--no-text-links] [--json | --brief | --degradation [--degradation-cap <n>]] [--out <file>]
|
|
150677
151118
|
|
|
150678
151119
|
The files a change can reach: dependants of the changed files, up to --depth, capped at --max-files, with related tests.
|
|
150679
151120
|
--reach symbol (default) follows the changed exported names: importers that use none of them, and files behind a
|
|
150680
151121
|
high fan-in hub, are omitted with a reason instead of reviewed; --reach file walks every reverse import edge.
|
|
150681
151122
|
Files that mention a string key the diff changed (i18n key, event, CSS class, config key) are added unless
|
|
150682
151123
|
--no-text-links. With --package the changed files are base...head at the package's pinned head.
|
|
150683
|
-
--package stores the record in the package; --previous/--final carry the dropped files across rounds
|
|
151124
|
+
--package stores the record in the package; --previous/--final carry the dropped files across rounds.
|
|
151125
|
+
--degradation prints the review-degradation payload instead: the one-hop importers of the module that holds each changed
|
|
151126
|
+
shared export (and of a barrel that re-exports it), from the code graph, under one cap shared by all of them
|
|
151127
|
+
(--degradation-cap, default 40), with the cap stated and the files it dropped named (the first 20, then a count).
|
|
151128
|
+
It always recomputes from the graph, also with --previous, never rewrites the package's record, and is refused together
|
|
151129
|
+
with --json, --brief or --out; --degradation-cap is refused without --degradation.`);
|
|
150684
151130
|
}
|
|
150685
151131
|
function printCompleteHelp() {
|
|
150686
151132
|
console.log(`keryx review complete <review-id-or-path> [--finding <id>] [--disposition <value>] [--evidence <text>]
|
|
@@ -151310,12 +151756,12 @@ var init_review = __esm(() => {
|
|
|
151310
151756
|
init_context_collect();
|
|
151311
151757
|
init_mutate();
|
|
151312
151758
|
init_compare_reference();
|
|
151759
|
+
init_roster();
|
|
151313
151760
|
init_coverage();
|
|
151314
151761
|
init_role_switches();
|
|
151315
151762
|
init_estimate_actual();
|
|
151316
151763
|
init_estimate();
|
|
151317
151764
|
init_json();
|
|
151318
|
-
init_round_split();
|
|
151319
151765
|
init_review_plan();
|
|
151320
151766
|
init_review_plan_eval();
|
|
151321
151767
|
init_coverage_complete();
|
|
@@ -151375,6 +151821,8 @@ var init_review = __esm(() => {
|
|
|
151375
151821
|
init_external_providers();
|
|
151376
151822
|
init_external_notice();
|
|
151377
151823
|
init_blast_radius();
|
|
151824
|
+
init_degradation();
|
|
151825
|
+
init_degradation_payload();
|
|
151378
151826
|
init_query();
|
|
151379
151827
|
init_live_codex_models();
|
|
151380
151828
|
init_select();
|
|
@@ -151514,7 +151962,9 @@ var init_review = __esm(() => {
|
|
|
151514
151962
|
"--out",
|
|
151515
151963
|
"--package",
|
|
151516
151964
|
"--reach",
|
|
151517
|
-
"--no-text-links"
|
|
151965
|
+
"--no-text-links",
|
|
151966
|
+
"--degradation",
|
|
151967
|
+
"--degradation-cap"
|
|
151518
151968
|
];
|
|
151519
151969
|
TIER_FLAGS = [
|
|
151520
151970
|
"--help",
|
|
@@ -160755,105 +161205,6 @@ var init_statusbar = __esm(() => {
|
|
|
160755
161205
|
CSI = `${ESC}[`;
|
|
160756
161206
|
});
|
|
160757
161207
|
|
|
160758
|
-
// src/lib/live-render.ts
|
|
160759
|
-
function stripAnsi(text) {
|
|
160760
|
-
return text.replace(ANSI_RE, "");
|
|
160761
|
-
}
|
|
160762
|
-
function isWideCodePoint(cp) {
|
|
160763
|
-
return cp >= 4352 && cp <= 4447 || cp >= 11904 && cp <= 12350 || cp >= 12353 && cp <= 13311 || cp >= 13312 && cp <= 19903 || cp >= 19968 && cp <= 40959 || cp >= 40960 && cp <= 42191 || cp >= 44032 && cp <= 55203 || cp >= 63744 && cp <= 64255 || cp >= 65072 && cp <= 65103 || cp >= 65280 && cp <= 65376 || cp >= 65504 && cp <= 65510 || cp >= 127744 && cp <= 129791 || cp >= 131072 && cp <= 262141;
|
|
160764
|
-
}
|
|
160765
|
-
function displayWidth(text) {
|
|
160766
|
-
const plain = stripAnsi(text);
|
|
160767
|
-
let width = 0;
|
|
160768
|
-
for (const ch of plain) {
|
|
160769
|
-
const cp = ch.codePointAt(0) ?? 0;
|
|
160770
|
-
width += isWideCodePoint(cp) ? 2 : 1;
|
|
160771
|
-
}
|
|
160772
|
-
return width;
|
|
160773
|
-
}
|
|
160774
|
-
function physicalRows(lines, cols) {
|
|
160775
|
-
if (cols <= 0) {
|
|
160776
|
-
return lines.length;
|
|
160777
|
-
}
|
|
160778
|
-
let rows = 0;
|
|
160779
|
-
for (const line of lines) {
|
|
160780
|
-
const width = displayWidth(line);
|
|
160781
|
-
rows += width === 0 ? 1 : Math.ceil(width / cols);
|
|
160782
|
-
}
|
|
160783
|
-
return rows;
|
|
160784
|
-
}
|
|
160785
|
-
function computeRepaint(opts) {
|
|
160786
|
-
const { prevRows, nextLines, cols, sync = false } = opts;
|
|
160787
|
-
let body = "";
|
|
160788
|
-
if (prevRows > 0) {
|
|
160789
|
-
body += "\r";
|
|
160790
|
-
if (prevRows > 1) {
|
|
160791
|
-
body += CURSOR_UP(prevRows - 1);
|
|
160792
|
-
}
|
|
160793
|
-
body += CLEAR_TO_END;
|
|
160794
|
-
}
|
|
160795
|
-
body += nextLines.join(`
|
|
160796
|
-
`);
|
|
160797
|
-
const output = sync ? `${SYNC_BEGIN}${body}${SYNC_END}` : body;
|
|
160798
|
-
return { output, rows: physicalRows(nextLines, cols) };
|
|
160799
|
-
}
|
|
160800
|
-
|
|
160801
|
-
class LiveMarkdownBlock {
|
|
160802
|
-
deps;
|
|
160803
|
-
pending = "";
|
|
160804
|
-
prevRows = 0;
|
|
160805
|
-
prevCols = 0;
|
|
160806
|
-
dirty = false;
|
|
160807
|
-
constructor(deps) {
|
|
160808
|
-
this.deps = deps;
|
|
160809
|
-
}
|
|
160810
|
-
append(text) {
|
|
160811
|
-
if (text.length === 0) {
|
|
160812
|
-
return;
|
|
160813
|
-
}
|
|
160814
|
-
this.pending += text;
|
|
160815
|
-
this.dirty = true;
|
|
160816
|
-
}
|
|
160817
|
-
flush() {
|
|
160818
|
-
if (!this.dirty) {
|
|
160819
|
-
return;
|
|
160820
|
-
}
|
|
160821
|
-
this.repaint();
|
|
160822
|
-
this.dirty = false;
|
|
160823
|
-
}
|
|
160824
|
-
finalize() {
|
|
160825
|
-
if (this.dirty) {
|
|
160826
|
-
this.repaint();
|
|
160827
|
-
this.dirty = false;
|
|
160828
|
-
}
|
|
160829
|
-
if (this.prevRows > 0 || this.pending.length > 0) {
|
|
160830
|
-
this.deps.out(`
|
|
160831
|
-
`);
|
|
160832
|
-
}
|
|
160833
|
-
this.pending = "";
|
|
160834
|
-
this.prevRows = 0;
|
|
160835
|
-
this.prevCols = 0;
|
|
160836
|
-
}
|
|
160837
|
-
repaint() {
|
|
160838
|
-
const cols = this.deps.cols();
|
|
160839
|
-
if (this.prevRows > 0 && cols !== this.prevCols) {
|
|
160840
|
-
this.deps.out(`
|
|
160841
|
-
`);
|
|
160842
|
-
this.prevRows = 0;
|
|
160843
|
-
}
|
|
160844
|
-
const lines = this.deps.render(this.pending).split(`
|
|
160845
|
-
`);
|
|
160846
|
-
const { output, rows } = computeRepaint({ prevRows: this.prevRows, nextLines: lines, cols, sync: this.deps.sync });
|
|
160847
|
-
this.deps.out(output);
|
|
160848
|
-
this.prevRows = rows;
|
|
160849
|
-
this.prevCols = cols;
|
|
160850
|
-
}
|
|
160851
|
-
}
|
|
160852
|
-
var ANSI_RE, CURSOR_UP = (n) => `\x1B[${n}A`, CLEAR_TO_END = "\x1B[0J", SYNC_BEGIN = "\x1B[?2026h", SYNC_END = "\x1B[?2026l";
|
|
160853
|
-
var init_live_render = __esm(() => {
|
|
160854
|
-
ANSI_RE = /\x1b\[[0-9;?]*[A-Za-z]/g;
|
|
160855
|
-
});
|
|
160856
|
-
|
|
160857
161208
|
// src/remote/rendering-mode.ts
|
|
160858
161209
|
function isRenderMode(value) {
|
|
160859
161210
|
return typeof value === "string" && RENDER_MODES.includes(value);
|
|
@@ -167231,7 +167582,15 @@ function unmountActiveTab(state) {
|
|
|
167231
167582
|
state.scroll.scrollTop = 0;
|
|
167232
167583
|
}
|
|
167233
167584
|
function mountTab(state, input, tabId) {
|
|
167585
|
+
const target = state.tabs.find((tab) => tab.id === tabId);
|
|
167586
|
+
if (target === undefined)
|
|
167587
|
+
return;
|
|
167588
|
+
const previous = state.tabs.find((tab) => tab.id === state.active);
|
|
167234
167589
|
unmountActiveTab(state);
|
|
167590
|
+
if (previous?.temporary && previous.id !== tabId) {
|
|
167591
|
+
state.tabs = state.tabs.filter((tab) => tab.id !== previous.id);
|
|
167592
|
+
state.temporaryParents.delete(previous.id);
|
|
167593
|
+
}
|
|
167235
167594
|
state.active = tabId;
|
|
167236
167595
|
paintTabs(state);
|
|
167237
167596
|
if (input.footerFor !== undefined) {
|
|
@@ -167240,7 +167599,7 @@ function mountTab(state, input, tabId) {
|
|
|
167240
167599
|
const availWidth = resolveModalAvailableWidth(state.chrome);
|
|
167241
167600
|
const size = resolveModalPanelSize(availWidth, state.chrome.renderer.height, input.contentRows);
|
|
167242
167601
|
const panelWidth = Math.min(availWidth, size.width);
|
|
167243
|
-
const cleanup = input.renderTab(tabId, state.body, {
|
|
167602
|
+
const cleanup = (target.renderTab ?? input.renderTab)(tabId, state.body, {
|
|
167244
167603
|
width: resolveModalInnerWidth(panelWidth) - MODAL_BODY_SCROLLBAR_COLS,
|
|
167245
167604
|
height: modalBodyRows(size.height)
|
|
167246
167605
|
});
|
|
@@ -167400,6 +167759,7 @@ function ensureHost(otui, chrome) {
|
|
|
167400
167759
|
open: false,
|
|
167401
167760
|
generation: 0,
|
|
167402
167761
|
tabs: [],
|
|
167762
|
+
temporaryParents: new Map,
|
|
167403
167763
|
active: "",
|
|
167404
167764
|
tabCleanup: undefined,
|
|
167405
167765
|
onClose: undefined,
|
|
@@ -167422,7 +167782,12 @@ function ensureHost(otui, chrome) {
|
|
|
167422
167782
|
const focused = r.currentFocusedRenderable;
|
|
167423
167783
|
const inBody = focused !== null && containsNode(state.scroll, focused);
|
|
167424
167784
|
if (key.name === "escape") {
|
|
167425
|
-
|
|
167785
|
+
const parent = state.temporaryParents.get(state.active);
|
|
167786
|
+
if (parent !== undefined && state.tabs.some((tab) => tab.id === parent)) {
|
|
167787
|
+
mountTab(state, state.input, parent);
|
|
167788
|
+
} else {
|
|
167789
|
+
closeHost(state, { restoreFocus: true, runOnClose: true });
|
|
167790
|
+
}
|
|
167426
167791
|
key.preventDefault();
|
|
167427
167792
|
key.stopPropagation();
|
|
167428
167793
|
return;
|
|
@@ -167513,6 +167878,39 @@ function makeHandle(state, generation) {
|
|
|
167513
167878
|
}
|
|
167514
167879
|
mountTab(state, state.input, id);
|
|
167515
167880
|
},
|
|
167881
|
+
addTab(tab, afterId) {
|
|
167882
|
+
if (state.generation !== generation || !state.open || state.input === undefined)
|
|
167883
|
+
return;
|
|
167884
|
+
if (state.tabs.some((entry) => entry.id === tab.id))
|
|
167885
|
+
return;
|
|
167886
|
+
const parent = afterId ?? state.temporaryParents.get(state.active) ?? state.active;
|
|
167887
|
+
const index = state.tabs.findIndex((entry) => entry.id === parent && !entry.temporary);
|
|
167888
|
+
if (index < 0)
|
|
167889
|
+
return;
|
|
167890
|
+
state.tabs.splice(index + 1, 0, { ...tab });
|
|
167891
|
+
if (tab.temporary)
|
|
167892
|
+
state.temporaryParents.set(tab.id, parent);
|
|
167893
|
+
mountTab(state, state.input, tab.id);
|
|
167894
|
+
state.chrome.renderer.requestRender();
|
|
167895
|
+
},
|
|
167896
|
+
removeTab(id) {
|
|
167897
|
+
if (state.generation !== generation || !state.open || state.input === undefined)
|
|
167898
|
+
return;
|
|
167899
|
+
const tab = state.tabs.find((entry) => entry.id === id);
|
|
167900
|
+
if (!tab?.temporary)
|
|
167901
|
+
return;
|
|
167902
|
+
if (state.active === id) {
|
|
167903
|
+
const parent = state.temporaryParents.get(id);
|
|
167904
|
+
if (parent === undefined)
|
|
167905
|
+
return;
|
|
167906
|
+
mountTab(state, state.input, parent);
|
|
167907
|
+
} else {
|
|
167908
|
+
state.tabs = state.tabs.filter((entry) => entry.id !== id);
|
|
167909
|
+
state.temporaryParents.delete(id);
|
|
167910
|
+
paintTabs(state);
|
|
167911
|
+
}
|
|
167912
|
+
state.chrome.renderer.requestRender();
|
|
167913
|
+
},
|
|
167516
167914
|
activeTab() {
|
|
167517
167915
|
return state.active;
|
|
167518
167916
|
}
|
|
@@ -167545,7 +167943,8 @@ function openModal(otui, chrome, input) {
|
|
|
167545
167943
|
state.generation += 1;
|
|
167546
167944
|
const generation = state.generation;
|
|
167547
167945
|
state.input = input;
|
|
167548
|
-
state.tabs = input.tabs;
|
|
167946
|
+
state.tabs = input.tabs.map((tab) => ({ ...tab }));
|
|
167947
|
+
state.temporaryParents.clear();
|
|
167549
167948
|
state.onClose = input.onClose;
|
|
167550
167949
|
paintHeader(state, input.title);
|
|
167551
167950
|
paintFooter(state, input.footer);
|
|
@@ -169445,6 +169844,44 @@ async function layersViewOf(dir, dispatch) {
|
|
|
169445
169844
|
waiver: readCoverageWaiver(dispatch)
|
|
169446
169845
|
};
|
|
169447
169846
|
}
|
|
169847
|
+
async function readDegradationFindings(packageDir) {
|
|
169848
|
+
const parsed = await readJson14(path375.join(packageDir, "findings.json"));
|
|
169849
|
+
if (!Array.isArray(parsed))
|
|
169850
|
+
return [];
|
|
169851
|
+
const out = [];
|
|
169852
|
+
for (const raw of parsed) {
|
|
169853
|
+
if (raw === null || typeof raw !== "object")
|
|
169854
|
+
continue;
|
|
169855
|
+
const f = raw;
|
|
169856
|
+
const screen = f.evidence_screen !== null && typeof f.evidence_screen === "object" ? f.evidence_screen : undefined;
|
|
169857
|
+
const hasField = f.degradation_class !== undefined || f.evidence_kind !== undefined || screen !== undefined;
|
|
169858
|
+
if (!isDegradationReviewer(f.reviewer) && !hasField)
|
|
169859
|
+
continue;
|
|
169860
|
+
out.push({
|
|
169861
|
+
id: str4(f.id) ?? "?",
|
|
169862
|
+
severity: str4(f.severity) ?? "?",
|
|
169863
|
+
degradationClass: str4(f.degradation_class),
|
|
169864
|
+
evidenceKind: str4(f.evidence_kind),
|
|
169865
|
+
unproven: screen !== undefined,
|
|
169866
|
+
originalSeverity: str4(screen?.original_severity),
|
|
169867
|
+
file: str4(f.file)
|
|
169868
|
+
});
|
|
169869
|
+
}
|
|
169870
|
+
return out;
|
|
169871
|
+
}
|
|
169872
|
+
function degradationLines(findings) {
|
|
169873
|
+
if (findings === undefined || findings.length === 0)
|
|
169874
|
+
return [];
|
|
169875
|
+
const unproven = findings.filter((f) => f.unproven).length;
|
|
169876
|
+
const head = `degradation: ${findings.length} finding(s)${unproven > 0 ? `, ${unproven} unproven` : ""}`;
|
|
169877
|
+
return [
|
|
169878
|
+
head,
|
|
169879
|
+
...findings.map((f) => {
|
|
169880
|
+
const proof = f.unproven ? `UNPROVEN${f.originalSeverity === undefined ? "" : ` (was ${f.originalSeverity})`}` : `evidence ${f.evidenceKind ?? "none"}`;
|
|
169881
|
+
return ` ${safeText(`${f.id} ${f.severity} ${f.degradationClass ?? "no class"}, ${proof}${f.file === undefined ? "" : ` ${f.file}`}`)}`;
|
|
169882
|
+
})
|
|
169883
|
+
];
|
|
169884
|
+
}
|
|
169448
169885
|
function pullLabel(review) {
|
|
169449
169886
|
if (review.repo !== null && review.number !== null)
|
|
169450
169887
|
return `${review.repo}#${review.number}`;
|
|
@@ -169460,10 +169897,11 @@ async function loadReviewDepth(cwd, listed) {
|
|
|
169460
169897
|
const waiver = dispatch?.mutationWaiver;
|
|
169461
169898
|
const mutationWaived = waiver !== null && typeof waiver === "object" && typeof waiver.operatorQuote === "string";
|
|
169462
169899
|
const parity = await readReviewParity(review.dir);
|
|
169900
|
+
const degradation = await readDegradationFindings(review.dir);
|
|
169463
169901
|
const timing = await packageEstimateLine(review.dir, { open: review.manifest.status !== "closed" });
|
|
169464
169902
|
const base = { reviewId: review.reviewId, pull: pullLabel(review), selected, mutation: await mutationOf(review.dir), mutationWaived, layersView: await layersViewOf(review.dir, dispatch), readiness: await computeReadiness(review.dir).catch(() => {
|
|
169465
169903
|
return;
|
|
169466
|
-
}), ...parityIsEmpty(parity) ? {} : { parity }, ...timing === undefined ? {} : { timing } };
|
|
169904
|
+
}), ...parityIsEmpty(parity) ? {} : { parity }, ...degradation.length === 0 ? {} : { degradation }, ...timing === undefined ? {} : { timing } };
|
|
169467
169905
|
if (mode === undefined) {
|
|
169468
169906
|
rows.push({ ...base, mode: "unrecorded", required: [], excluded: [], missing: [], filesKnown: false });
|
|
169469
169907
|
continue;
|
|
@@ -169522,7 +169960,7 @@ function layersDepthLines(view) {
|
|
|
169522
169960
|
function formatReviewDepthRowLines(row) {
|
|
169523
169961
|
const head = `${row.pull} ${row.reviewId} mode ${row.mode}`;
|
|
169524
169962
|
const lines = [head];
|
|
169525
|
-
const layers = [...readinessDepthLines(row.readiness), ...layersDepthLines(row.layersView), ...parityLines(row.parity), ...row.timing === undefined ? [] : [row.timing]].map((line) => ` ${line}`);
|
|
169963
|
+
const layers = [...readinessDepthLines(row.readiness), ...layersDepthLines(row.layersView), ...parityLines(row.parity), ...degradationLines(row.degradation), ...row.timing === undefined ? [] : [row.timing]].map((line) => ` ${line}`);
|
|
169526
169964
|
if (row.mode === "unrecorded") {
|
|
169527
169965
|
lines.push(" no dispatch record: the reviewer set is not known", ` ${mutationLine(row.mutation, row.mutationWaived)}`, ...layers);
|
|
169528
169966
|
return lines;
|
|
@@ -169585,9 +170023,11 @@ ${blocks.join(`
|
|
|
169585
170023
|
`)}
|
|
169586
170024
|
`;
|
|
169587
170025
|
}
|
|
169588
|
-
var REVIEW_DEPTH_EMPTY = "No managed review has recorded a dispatch yet.", ZERO;
|
|
170026
|
+
var REVIEW_DEPTH_EMPTY = "No managed review has recorded a dispatch yet.", ZERO, str4 = (value) => typeof value === "string" && value !== "" ? value : undefined;
|
|
169589
170027
|
var init_review_panel = __esm(() => {
|
|
170028
|
+
init_live_render();
|
|
169590
170029
|
init_shell_quote();
|
|
170030
|
+
init_degradation();
|
|
169591
170031
|
init_packages();
|
|
169592
170032
|
init_coverage();
|
|
169593
170033
|
init_role_switches();
|
|
@@ -177691,7 +178131,7 @@ function mountApprovalsSidebar(options) {
|
|
|
177691
178131
|
if (known.has(view.approvalId))
|
|
177692
178132
|
continue;
|
|
177693
178133
|
known.add(view.approvalId);
|
|
177694
|
-
notice(`Remote approval pending: ${view.summary.replace(
|
|
178134
|
+
notice(`Remote approval pending: ${view.summary.replace(CONTROL_CHARACTERS3, " ")} - ${APPROVALS_COMMAND} to answer
|
|
177695
178135
|
`);
|
|
177696
178136
|
}
|
|
177697
178137
|
}
|
|
@@ -177721,7 +178161,7 @@ function mountApprovalsSidebar(options) {
|
|
|
177721
178161
|
}
|
|
177722
178162
|
};
|
|
177723
178163
|
}
|
|
177724
|
-
var
|
|
178164
|
+
var CONTROL_CHARACTERS3, APPROVALS_POLL_MS = 3000;
|
|
177725
178165
|
var init_approvals_sidebar = __esm(() => {
|
|
177726
178166
|
init_agent_commands();
|
|
177727
178167
|
init_serve_approvals_store();
|
|
@@ -177731,7 +178171,7 @@ var init_approvals_sidebar = __esm(() => {
|
|
|
177731
178171
|
init_theme_repaint();
|
|
177732
178172
|
init_theme_text();
|
|
177733
178173
|
init_transcript_blocks();
|
|
177734
|
-
|
|
178174
|
+
CONTROL_CHARACTERS3 = /[\u0000-\u001f\u007f]/g;
|
|
177735
178175
|
});
|
|
177736
178176
|
|
|
177737
178177
|
// src/intake/ports.ts
|
|
@@ -178662,11 +179102,11 @@ var init_actions = __esm(() => {
|
|
|
178662
179102
|
function record6(value) {
|
|
178663
179103
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
178664
179104
|
}
|
|
178665
|
-
function
|
|
179105
|
+
function str5(value) {
|
|
178666
179106
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
178667
179107
|
}
|
|
178668
179108
|
function login2(value) {
|
|
178669
|
-
return
|
|
179109
|
+
return str5(record6(value)?.["login"]);
|
|
178670
179110
|
}
|
|
178671
179111
|
function rows2(stdout) {
|
|
178672
179112
|
let data;
|
|
@@ -178689,18 +179129,18 @@ function parseAssignedIssues(repo, stdout) {
|
|
|
178689
179129
|
const events = [];
|
|
178690
179130
|
for (const r of list) {
|
|
178691
179131
|
const n = numberOf(r);
|
|
178692
|
-
const updated =
|
|
179132
|
+
const updated = str5(r["updatedAt"]);
|
|
178693
179133
|
if (n === undefined || updated === undefined)
|
|
178694
179134
|
continue;
|
|
178695
|
-
const url =
|
|
179135
|
+
const url = str5(r["url"]);
|
|
178696
179136
|
const author = login2(r["author"]);
|
|
178697
|
-
const body =
|
|
179137
|
+
const body = str5(r["body"]);
|
|
178698
179138
|
events.push({
|
|
178699
179139
|
key: `issue:${repo}#${n}`,
|
|
178700
179140
|
kind: "issue",
|
|
178701
179141
|
repo,
|
|
178702
179142
|
ref: n,
|
|
178703
|
-
title:
|
|
179143
|
+
title: str5(r["title"]) ?? `#${n}`,
|
|
178704
179144
|
...url !== undefined ? { url } : {},
|
|
178705
179145
|
stamp: updated,
|
|
178706
179146
|
...author !== undefined ? { author } : {},
|
|
@@ -178716,17 +179156,17 @@ function parseReviewRequests(repo, stdout) {
|
|
|
178716
179156
|
const events = [];
|
|
178717
179157
|
for (const r of list) {
|
|
178718
179158
|
const n = numberOf(r);
|
|
178719
|
-
const updated =
|
|
179159
|
+
const updated = str5(r["updatedAt"]);
|
|
178720
179160
|
if (n === undefined || updated === undefined)
|
|
178721
179161
|
continue;
|
|
178722
|
-
const url =
|
|
179162
|
+
const url = str5(r["url"]);
|
|
178723
179163
|
const author = login2(r["author"]);
|
|
178724
179164
|
events.push({
|
|
178725
179165
|
key: `review:${repo}#${n}`,
|
|
178726
179166
|
kind: "review",
|
|
178727
179167
|
repo,
|
|
178728
179168
|
ref: n,
|
|
178729
|
-
title:
|
|
179169
|
+
title: str5(r["title"]) ?? `#${n}`,
|
|
178730
179170
|
...url !== undefined ? { url } : {},
|
|
178731
179171
|
stamp: updated,
|
|
178732
179172
|
...author !== undefined ? { author } : {}
|
|
@@ -178744,12 +179184,12 @@ function parseOwnPrs(repo, stdout) {
|
|
|
178744
179184
|
const n = numberOf(pr);
|
|
178745
179185
|
if (n === undefined)
|
|
178746
179186
|
continue;
|
|
178747
|
-
const branch =
|
|
179187
|
+
const branch = str5(pr["headRefName"]);
|
|
178748
179188
|
if (branch !== undefined)
|
|
178749
179189
|
branches.add(branch);
|
|
178750
179190
|
const prAuthor = login2(pr["author"]);
|
|
178751
|
-
const prTitle =
|
|
178752
|
-
const prUrl =
|
|
179191
|
+
const prTitle = str5(pr["title"]) ?? `#${n}`;
|
|
179192
|
+
const prUrl = str5(pr["url"]);
|
|
178753
179193
|
const raw = pr["comments"];
|
|
178754
179194
|
if (!Array.isArray(raw))
|
|
178755
179195
|
continue;
|
|
@@ -178757,15 +179197,15 @@ function parseOwnPrs(repo, stdout) {
|
|
|
178757
179197
|
const r = record6(c);
|
|
178758
179198
|
if (r === undefined)
|
|
178759
179199
|
continue;
|
|
178760
|
-
const id =
|
|
178761
|
-
const created =
|
|
179200
|
+
const id = str5(r["id"]);
|
|
179201
|
+
const created = str5(r["createdAt"]);
|
|
178762
179202
|
if (id === undefined || created === undefined)
|
|
178763
179203
|
continue;
|
|
178764
179204
|
const author = login2(r["author"]);
|
|
178765
179205
|
if (r["viewerDidAuthor"] === true || author !== undefined && author === prAuthor)
|
|
178766
179206
|
continue;
|
|
178767
|
-
const url =
|
|
178768
|
-
const body =
|
|
179207
|
+
const url = str5(r["url"]) ?? prUrl;
|
|
179208
|
+
const body = str5(r["body"]);
|
|
178769
179209
|
comments.push({
|
|
178770
179210
|
key: `comment:${repo}#${n}:${id}`,
|
|
178771
179211
|
kind: "comment",
|
|
@@ -178788,17 +179228,17 @@ function parseFailedRuns(repo, stdout, branches) {
|
|
|
178788
179228
|
const events = [];
|
|
178789
179229
|
for (const r of list) {
|
|
178790
179230
|
const id = typeof r["databaseId"] === "number" ? String(r["databaseId"]) : undefined;
|
|
178791
|
-
const created =
|
|
178792
|
-
const branch =
|
|
179231
|
+
const created = str5(r["createdAt"]);
|
|
179232
|
+
const branch = str5(r["headBranch"]);
|
|
178793
179233
|
if (id === undefined || created === undefined || branch === undefined || !branches.has(branch))
|
|
178794
179234
|
continue;
|
|
178795
|
-
const url =
|
|
179235
|
+
const url = str5(r["url"]);
|
|
178796
179236
|
events.push({
|
|
178797
179237
|
key: `ci:${repo}:${id}`,
|
|
178798
179238
|
kind: "ci",
|
|
178799
179239
|
repo,
|
|
178800
179240
|
ref: id,
|
|
178801
|
-
title: `${
|
|
179241
|
+
title: `${str5(r["displayTitle"]) ?? "workflow run"} (${branch})`,
|
|
178802
179242
|
...url !== undefined ? { url } : {},
|
|
178803
179243
|
stamp: created
|
|
178804
179244
|
});
|
|
@@ -179607,7 +180047,7 @@ function resolveDeps(deps) {
|
|
|
179607
180047
|
};
|
|
179608
180048
|
}
|
|
179609
180049
|
function clean2(text, max = 90) {
|
|
179610
|
-
const flat = text.replace(
|
|
180050
|
+
const flat = text.replace(CONTROL_CHARACTERS4, " ").replace(/\s+/g, " ").trim();
|
|
179611
180051
|
return flat.length <= max ? flat : `${flat.slice(0, Math.max(0, max - 1))}\u2026`;
|
|
179612
180052
|
}
|
|
179613
180053
|
function intakeActionsFor(card) {
|
|
@@ -180028,7 +180468,7 @@ function mountIntakeSidebar(options) {
|
|
|
180028
180468
|
var INTAKE_COMMAND = "/intake", INTAKE_USAGE, INTAKE_POLL_MS = 5000, INTAKE_TABS, INTAKE_FOOTER, decideWithRealPorts = (root, cardId, action, options) => {
|
|
180029
180469
|
ensureRealIntakePorts();
|
|
180030
180470
|
return decideIntakeCard(root, cardId, action, options);
|
|
180031
|
-
},
|
|
180471
|
+
}, CONTROL_CHARACTERS4, ACTION_LABEL, KEY_ACTIONS, EMPTY4;
|
|
180032
180472
|
var init_intake_surface = __esm(() => {
|
|
180033
180473
|
init_intake_ports();
|
|
180034
180474
|
init_actions();
|
|
@@ -180057,7 +180497,7 @@ var init_intake_surface = __esm(() => {
|
|
|
180057
180497
|
{ key: "p", label: "\u043F\u0430\u0443\u0437\u0430/\u043F\u0443\u0441\u043A" },
|
|
180058
180498
|
{ key: "esc", label: "\u0437\u0430\u043A\u0440\u044B\u0442\u044C" }
|
|
180059
180499
|
];
|
|
180060
|
-
|
|
180500
|
+
CONTROL_CHARACTERS4 = /[\u0000-\u001f\u007f-\u009f]/g;
|
|
180061
180501
|
ACTION_LABEL = {
|
|
180062
180502
|
take: "\u0432\u0437\u044F\u0442\u044C",
|
|
180063
180503
|
decline: "\u043E\u0442\u043A\u043B\u043E\u043D\u0438\u0442\u044C",
|
|
@@ -195655,9 +196095,11 @@ class ForegroundOperationOwner {
|
|
|
195655
196095
|
}
|
|
195656
196096
|
cancel(reason) {
|
|
195657
196097
|
const controller = this.active?.controller;
|
|
195658
|
-
if (controller
|
|
195659
|
-
|
|
196098
|
+
if (controller === undefined || controller.signal.aborted) {
|
|
196099
|
+
return false;
|
|
195660
196100
|
}
|
|
196101
|
+
controller.abort(reason);
|
|
196102
|
+
return true;
|
|
195661
196103
|
}
|
|
195662
196104
|
invalidateSession() {
|
|
195663
196105
|
this.sessionGeneration += 1;
|
|
@@ -195688,6 +196130,10 @@ function createForegroundOperationOwner() {
|
|
|
195688
196130
|
class ForegroundForceHandoff {
|
|
195689
196131
|
pending = [];
|
|
195690
196132
|
awaitingSettlement = false;
|
|
196133
|
+
handoffGeneration = 0;
|
|
196134
|
+
get generation() {
|
|
196135
|
+
return this.handoffGeneration;
|
|
196136
|
+
}
|
|
195691
196137
|
enqueue(item) {
|
|
195692
196138
|
this.pending.push(item);
|
|
195693
196139
|
if (this.awaitingSettlement)
|
|
@@ -195696,8 +196142,19 @@ class ForegroundForceHandoff {
|
|
|
195696
196142
|
return true;
|
|
195697
196143
|
}
|
|
195698
196144
|
clear() {
|
|
196145
|
+
this.handoffGeneration += 1;
|
|
196146
|
+
this.pending.length = 0;
|
|
196147
|
+
this.awaitingSettlement = false;
|
|
196148
|
+
}
|
|
196149
|
+
releaseAwaitingSettlement() {
|
|
196150
|
+
this.awaitingSettlement = false;
|
|
196151
|
+
}
|
|
196152
|
+
drainPending() {
|
|
196153
|
+
this.handoffGeneration += 1;
|
|
196154
|
+
const items = [...this.pending];
|
|
195699
196155
|
this.pending.length = 0;
|
|
195700
196156
|
this.awaitingSettlement = false;
|
|
196157
|
+
return items;
|
|
195701
196158
|
}
|
|
195702
196159
|
get isAwaitingSettlement() {
|
|
195703
196160
|
return this.awaitingSettlement;
|
|
@@ -195733,21 +196190,32 @@ async function runAfterForegroundSettlement(owner, action) {
|
|
|
195733
196190
|
await owner.settled();
|
|
195734
196191
|
if (!owner.isDisposed && owner.generation === generation) {
|
|
195735
196192
|
action();
|
|
196193
|
+
return true;
|
|
195736
196194
|
}
|
|
196195
|
+
return false;
|
|
195737
196196
|
}
|
|
195738
196197
|
async function forceForegroundQueueItem(owner, handoff, item, deps) {
|
|
195739
196198
|
const ownsSettlement = handoff.enqueue(item);
|
|
195740
|
-
|
|
195741
|
-
deps.
|
|
196199
|
+
const handoffGeneration = handoff.generation;
|
|
196200
|
+
const interrupted = owner.cancel(deps.cancelReason ?? "queue item forced");
|
|
196201
|
+
deps.announce?.(interrupted);
|
|
195742
196202
|
if (!ownsSettlement) {
|
|
195743
196203
|
return;
|
|
195744
196204
|
}
|
|
195745
|
-
await runAfterForegroundSettlement(owner, () => {
|
|
196205
|
+
const settlementActionRan = await runAfterForegroundSettlement(owner, () => {
|
|
196206
|
+
if (deps.isBusy?.() === true) {
|
|
196207
|
+
handoff.releaseAwaitingSettlement();
|
|
196208
|
+
deps.resume?.();
|
|
196209
|
+
return;
|
|
196210
|
+
}
|
|
195746
196211
|
const next = handoff.takeAfterSettlement();
|
|
195747
196212
|
if (next !== undefined) {
|
|
195748
196213
|
deps.run(next);
|
|
195749
196214
|
}
|
|
195750
196215
|
});
|
|
196216
|
+
if (!settlementActionRan && handoff.generation === handoffGeneration) {
|
|
196217
|
+
deps.requeue?.(handoff.drainPending());
|
|
196218
|
+
}
|
|
195751
196219
|
}
|
|
195752
196220
|
function withTurnScopedIo(io, turn) {
|
|
195753
196221
|
const scoped = Object.create(io);
|
|
@@ -196912,6 +197380,46 @@ function openSidebarCard(otui, chrome, cardId, options) {
|
|
|
196912
197380
|
const tab = tabOf(tabId);
|
|
196913
197381
|
if (tab === undefined)
|
|
196914
197382
|
return;
|
|
197383
|
+
if (options.snapshot().items?.[tabId] !== undefined) {
|
|
197384
|
+
const rows = new otui.BoxRenderable(chrome.renderer, {
|
|
197385
|
+
id: `sb-card-items-${tabId}`,
|
|
197386
|
+
flexDirection: "column",
|
|
197387
|
+
flexShrink: 0
|
|
197388
|
+
});
|
|
197389
|
+
body.add(rows);
|
|
197390
|
+
const paint = () => {
|
|
197391
|
+
for (const child of [...rows.getChildren()]) {
|
|
197392
|
+
rows.remove(child);
|
|
197393
|
+
child.destroy();
|
|
197394
|
+
}
|
|
197395
|
+
const snapshot = options.snapshot();
|
|
197396
|
+
rows.add(new otui.TextRenderable(chrome.renderer, {
|
|
197397
|
+
id: `sb-card-items-${tabId}-header`,
|
|
197398
|
+
content: snapshot.sections?.[tabId] ?? "Nothing to show yet.",
|
|
197399
|
+
fg: roleColor("text")
|
|
197400
|
+
}));
|
|
197401
|
+
for (const item of snapshot.items?.[tabId] ?? []) {
|
|
197402
|
+
rows.add(new otui.TextRenderable(chrome.renderer, {
|
|
197403
|
+
id: `sb-card-item-${tabId}-${item.id}`,
|
|
197404
|
+
content: item.text,
|
|
197405
|
+
fg: roleColor("text"),
|
|
197406
|
+
onMouseDown: () => {
|
|
197407
|
+
if (options.renderItem === undefined)
|
|
197408
|
+
return;
|
|
197409
|
+
handle?.addTab({
|
|
197410
|
+
id: `detail:${tabId}:${item.id}`,
|
|
197411
|
+
label: item.label,
|
|
197412
|
+
temporary: true,
|
|
197413
|
+
renderTab: (_id, target, context) => options.renderItem?.(tabId, item, target, context)
|
|
197414
|
+
}, tabId);
|
|
197415
|
+
}
|
|
197416
|
+
}));
|
|
197417
|
+
}
|
|
197418
|
+
chrome.renderer.requestRender();
|
|
197419
|
+
};
|
|
197420
|
+
paint();
|
|
197421
|
+
return options.subscribe?.(paint);
|
|
197422
|
+
}
|
|
196915
197423
|
const lines = tab.lines(ctx.width);
|
|
196916
197424
|
const parent = body;
|
|
196917
197425
|
parent.add(new otui.TextRenderable(chrome.renderer, { id: `sb-card-detail-${tabId}`, content: lines.join(`
|
|
@@ -197002,8 +197510,9 @@ var init_sidebar_cards = __esm(() => {
|
|
|
197002
197510
|
],
|
|
197003
197511
|
rows: [
|
|
197004
197512
|
{ sections: ["status"], required: true, fallback: "idle" },
|
|
197005
|
-
{ sections: ["
|
|
197006
|
-
{ sections: ["
|
|
197513
|
+
{ sections: ["subagents"], required: false, fallback: "" },
|
|
197514
|
+
{ sections: ["jobs"], required: false, fallback: "" },
|
|
197515
|
+
{ sections: ["plan"], required: false, fallback: "" }
|
|
197007
197516
|
]
|
|
197008
197517
|
},
|
|
197009
197518
|
{
|
|
@@ -197090,405 +197599,6 @@ var init_sidebar_cards = __esm(() => {
|
|
|
197090
197599
|
];
|
|
197091
197600
|
});
|
|
197092
197601
|
|
|
197093
|
-
// src/tui/side-worker.ts
|
|
197094
|
-
function isSideWorkerId(id) {
|
|
197095
|
-
return id.startsWith(SIDE_WORKER_ID_PREFIX);
|
|
197096
|
-
}
|
|
197097
|
-
function buildSideWorkerPrompt(opts) {
|
|
197098
|
-
const maxRecent = opts.maxRecent ?? 10;
|
|
197099
|
-
const recent = opts.recentHistory.slice(-maxRecent);
|
|
197100
|
-
const lines = [
|
|
197101
|
-
"## Live main-agent state (read-only snapshot)",
|
|
197102
|
-
`phase: ${opts.snapshot.phase || "unknown"}`
|
|
197103
|
-
];
|
|
197104
|
-
if (opts.snapshot.mainDetail !== undefined && opts.snapshot.mainDetail.length > 0) {
|
|
197105
|
-
lines.push(`detail: ${opts.snapshot.mainDetail}`);
|
|
197106
|
-
}
|
|
197107
|
-
if (opts.snapshot.elapsedSec !== undefined) {
|
|
197108
|
-
lines.push(`elapsed: ${opts.snapshot.elapsedSec.toFixed(1)}s`);
|
|
197109
|
-
}
|
|
197110
|
-
lines.push("", "## Recent main conversation (truncated; for context only)");
|
|
197111
|
-
if (recent.length === 0) {
|
|
197112
|
-
lines.push("(empty)");
|
|
197113
|
-
} else {
|
|
197114
|
-
for (const m of recent) {
|
|
197115
|
-
const body = m.content.length > 400 ? `${m.content.slice(0, 397)}\u2026` : m.content;
|
|
197116
|
-
lines.push(`### ${m.role}`, body, "");
|
|
197117
|
-
}
|
|
197118
|
-
}
|
|
197119
|
-
lines.push("## Your task", "Answer the user's SIDE question below. Do NOT continue or take over the main task.", "Be brief. Prefer facts already visible above; use read-only tools only if essential.", "If the question is about what the main agent is doing, use the live snapshot.", "", "## Side question", opts.question.trim());
|
|
197120
|
-
return lines.join(`
|
|
197121
|
-
`);
|
|
197122
|
-
}
|
|
197123
|
-
function buildSideWorkerSystemInstruction(providerId, modelId) {
|
|
197124
|
-
return `You are a keryx SIDE worker answering a user question while the MAIN agent is busy.
|
|
197125
|
-
` + `Rules:
|
|
197126
|
-
` + `- Answer ONLY the side question; do not continue the main task.
|
|
197127
|
-
` + `- Do not call shell_exec or any mutating tool. Read-only tools only if needed.
|
|
197128
|
-
` + `- Be concise (prefer bullets). Lead with the answer.
|
|
197129
|
-
` + `- Provider/model: ${providerId}/${modelId}.
|
|
197130
|
-
`;
|
|
197131
|
-
}
|
|
197132
|
-
function sideWorkerLabel(seq) {
|
|
197133
|
-
return `side-${seq}`;
|
|
197134
|
-
}
|
|
197135
|
-
var SIDE_WORKER_ID_PREFIX = "side:";
|
|
197136
|
-
|
|
197137
|
-
// src/tui/main-queue.ts
|
|
197138
|
-
function parseQueueCommand(args) {
|
|
197139
|
-
const parts = args.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
197140
|
-
const action = parts[0]?.toLowerCase();
|
|
197141
|
-
if (action !== "remove" && action !== "edit" && action !== "force") {
|
|
197142
|
-
return;
|
|
197143
|
-
}
|
|
197144
|
-
const raw = parts[1];
|
|
197145
|
-
if (raw === undefined) {
|
|
197146
|
-
return { action, position: 1 };
|
|
197147
|
-
}
|
|
197148
|
-
if (!/^\d+$/.test(raw)) {
|
|
197149
|
-
return;
|
|
197150
|
-
}
|
|
197151
|
-
const position = Number.parseInt(raw, 10);
|
|
197152
|
-
if (position < 1) {
|
|
197153
|
-
return;
|
|
197154
|
-
}
|
|
197155
|
-
return { action, position };
|
|
197156
|
-
}
|
|
197157
|
-
function formatMainQueueMarker(index) {
|
|
197158
|
-
const n = index + 1;
|
|
197159
|
-
return `> q${n} (${n})`;
|
|
197160
|
-
}
|
|
197161
|
-
function removeMainQueueItem(items, index) {
|
|
197162
|
-
if (index < 0 || index >= items.length)
|
|
197163
|
-
return [...items];
|
|
197164
|
-
const next = [...items];
|
|
197165
|
-
next.splice(index, 1);
|
|
197166
|
-
return next;
|
|
197167
|
-
}
|
|
197168
|
-
function editMainQueueItem(items, index) {
|
|
197169
|
-
if (index < 0 || index >= items.length)
|
|
197170
|
-
return;
|
|
197171
|
-
const rest = [...items];
|
|
197172
|
-
const removed = rest.splice(index, 1)[0];
|
|
197173
|
-
return { text: removed.question, rest, removed };
|
|
197174
|
-
}
|
|
197175
|
-
function reinsertMainQueueItem(items, at, item) {
|
|
197176
|
-
const next = [...items];
|
|
197177
|
-
const insertAt = Math.max(0, Math.min(at, next.length));
|
|
197178
|
-
next.splice(insertAt, 0, item);
|
|
197179
|
-
return next;
|
|
197180
|
-
}
|
|
197181
|
-
function pendingQueueEditFor(removed, at) {
|
|
197182
|
-
return { id: removed.id, at, ...removed.source !== undefined ? { source: removed.source } : {} };
|
|
197183
|
-
}
|
|
197184
|
-
function reinsertEditedMainQueueItem(items, edit, question, displayQuestion) {
|
|
197185
|
-
return reinsertMainQueueItem(items, edit.at, {
|
|
197186
|
-
id: edit.id,
|
|
197187
|
-
question,
|
|
197188
|
-
displayQuestion,
|
|
197189
|
-
...edit.source !== undefined ? { source: edit.source } : {}
|
|
197190
|
-
});
|
|
197191
|
-
}
|
|
197192
|
-
function dropQueuedBySource(items, source) {
|
|
197193
|
-
const kept = [];
|
|
197194
|
-
const dropped = [];
|
|
197195
|
-
for (const item of items) {
|
|
197196
|
-
(item.source === source ? dropped : kept).push(item);
|
|
197197
|
-
}
|
|
197198
|
-
return { kept, dropped };
|
|
197199
|
-
}
|
|
197200
|
-
function drainIdleMainQueue(items, options) {
|
|
197201
|
-
if (!options.isIdle())
|
|
197202
|
-
return false;
|
|
197203
|
-
const next = options.takeForced() ?? items.shift();
|
|
197204
|
-
if (next === undefined)
|
|
197205
|
-
return false;
|
|
197206
|
-
options.dispatch(next);
|
|
197207
|
-
return true;
|
|
197208
|
-
}
|
|
197209
|
-
function drainNext(options) {
|
|
197210
|
-
for (let budget = options.pending();budget > 0; budget--) {
|
|
197211
|
-
const before = options.pending();
|
|
197212
|
-
const next = options.take();
|
|
197213
|
-
options.paint();
|
|
197214
|
-
if (next === undefined)
|
|
197215
|
-
return "empty";
|
|
197216
|
-
options.run(next);
|
|
197217
|
-
if (options.isBusy())
|
|
197218
|
-
return "started";
|
|
197219
|
-
if (options.pending() >= before)
|
|
197220
|
-
return "exhausted";
|
|
197221
|
-
}
|
|
197222
|
-
return "empty";
|
|
197223
|
-
}
|
|
197224
|
-
function createMainQueueDrain(deps) {
|
|
197225
|
-
let detached = 0;
|
|
197226
|
-
const workInFlight = () => deps.turnWorkInFlight() || detached > 0;
|
|
197227
|
-
const drain = () => {
|
|
197228
|
-
const outcome = drainNext({
|
|
197229
|
-
take: () => deps.forced.takeNext() ?? deps.queue().shift(),
|
|
197230
|
-
pending: () => deps.forced.pendingCount + deps.queue().length,
|
|
197231
|
-
paint: deps.paint,
|
|
197232
|
-
run: deps.run,
|
|
197233
|
-
isBusy: workInFlight
|
|
197234
|
-
});
|
|
197235
|
-
if (outcome === "empty")
|
|
197236
|
-
deps.onSettle();
|
|
197237
|
-
return outcome;
|
|
197238
|
-
};
|
|
197239
|
-
const drainWhenIdle = () => {
|
|
197240
|
-
if (workInFlight())
|
|
197241
|
-
return;
|
|
197242
|
-
drain();
|
|
197243
|
-
};
|
|
197244
|
-
return {
|
|
197245
|
-
onForegroundOperationComplete() {
|
|
197246
|
-
if (deps.forced.isAwaitingSettlement)
|
|
197247
|
-
return;
|
|
197248
|
-
drain();
|
|
197249
|
-
},
|
|
197250
|
-
onTurnSettle() {
|
|
197251
|
-
if (deps.forced.isAwaitingSettlement)
|
|
197252
|
-
return;
|
|
197253
|
-
drain();
|
|
197254
|
-
},
|
|
197255
|
-
onLeaseRelease: drainWhenIdle,
|
|
197256
|
-
detach(work) {
|
|
197257
|
-
detached += 1;
|
|
197258
|
-
const end = () => {
|
|
197259
|
-
detached -= 1;
|
|
197260
|
-
drainWhenIdle();
|
|
197261
|
-
};
|
|
197262
|
-
work.then(end, (error) => {
|
|
197263
|
-
try {
|
|
197264
|
-
deps.onDetachedFailure(error);
|
|
197265
|
-
} catch {}
|
|
197266
|
-
end();
|
|
197267
|
-
});
|
|
197268
|
-
},
|
|
197269
|
-
workInFlight
|
|
197270
|
-
};
|
|
197271
|
-
}
|
|
197272
|
-
function describePendingCall(tool, inputJson) {
|
|
197273
|
-
let shown = tool;
|
|
197274
|
-
try {
|
|
197275
|
-
const parsed = JSON.parse(inputJson);
|
|
197276
|
-
if (parsed !== null && typeof parsed === "object") {
|
|
197277
|
-
const command = parsed.command;
|
|
197278
|
-
if (typeof command === "string" && command.trim().length > 0)
|
|
197279
|
-
shown = command;
|
|
197280
|
-
}
|
|
197281
|
-
} catch {}
|
|
197282
|
-
const oneLine = shown.replace(/\s+/g, " ").trim();
|
|
197283
|
-
return oneLine.length > PENDING_CALL_CHARS ? `${oneLine.slice(0, PENDING_CALL_CHARS - 1)}\u2026` : oneLine;
|
|
197284
|
-
}
|
|
197285
|
-
function queueWaitReason(wait) {
|
|
197286
|
-
return wait.kind === "turn" ? "waits for the current turn" : `waits for approval of: ${wait.call}`;
|
|
197287
|
-
}
|
|
197288
|
-
function formatQueueRow(index, text, wait) {
|
|
197289
|
-
return `${formatMainQueueMarker(index)} ${text} \u2014 queued \xB7 ${queueWaitReason(wait)}`;
|
|
197290
|
-
}
|
|
197291
|
-
function formatQueueListing(texts, wait) {
|
|
197292
|
-
if (texts.length === 0)
|
|
197293
|
-
return ["main queue is empty."];
|
|
197294
|
-
return [...texts.map((text, index) => formatQueueRow(index, text, wait)), QUEUE_FORCE_HELP];
|
|
197295
|
-
}
|
|
197296
|
-
var QUEUE_FORCE_HELP = "[Force] interrupts the current turn and runs this message now \xB7 [Edit] moves it back to the composer \xB7 [Delete] removes it", PENDING_CALL_CHARS = 60;
|
|
197297
|
-
|
|
197298
|
-
// src/tui/main-queue-view.ts
|
|
197299
|
-
function buildMainQueueRow(otui, r, params) {
|
|
197300
|
-
const theme = getTheme();
|
|
197301
|
-
const row = new otui.BoxRenderable(r, { id: `mq-${params.id}`, width: "100%", flexDirection: "row" });
|
|
197302
|
-
row.add(new otui.TextRenderable(r, {
|
|
197303
|
-
id: `mq-${params.id}-t`,
|
|
197304
|
-
flexGrow: 1,
|
|
197305
|
-
minWidth: 0,
|
|
197306
|
-
content: otui.t`${dimChunk(otui, formatQueueRow(params.index, params.text, params.wait))}`
|
|
197307
|
-
}));
|
|
197308
|
-
const force = smallActionButton(otui, r, "Force", `mq-force-${params.id}`, theme.focus, params.onForce);
|
|
197309
|
-
const edit = smallActionButton(otui, r, "Edit", `mq-edit-${params.id}`, theme.text, params.onEdit);
|
|
197310
|
-
const del = smallActionButton(otui, r, "Delete", `mq-del-${params.id}`, theme.error, params.onDelete);
|
|
197311
|
-
row.add(force.box);
|
|
197312
|
-
row.add(edit.box);
|
|
197313
|
-
row.add(del.box);
|
|
197314
|
-
return {
|
|
197315
|
-
box: row,
|
|
197316
|
-
buttons: { force, edit, delete: del },
|
|
197317
|
-
setRowActive: (active) => {
|
|
197318
|
-
row.backgroundColor = active ? theme.highlight : undefined;
|
|
197319
|
-
}
|
|
197320
|
-
};
|
|
197321
|
-
}
|
|
197322
|
-
function buildMainQueueHelp(otui, r) {
|
|
197323
|
-
return new otui.TextRenderable(r, {
|
|
197324
|
-
id: "mq-help",
|
|
197325
|
-
width: "100%",
|
|
197326
|
-
content: otui.t`${dimChunk(otui, QUEUE_FORCE_HELP)}`
|
|
197327
|
-
});
|
|
197328
|
-
}
|
|
197329
|
-
var init_main_queue_view = __esm(() => {
|
|
197330
|
-
init_action_button();
|
|
197331
|
-
init_theme();
|
|
197332
|
-
init_theme_text();
|
|
197333
|
-
});
|
|
197334
|
-
|
|
197335
|
-
// src/tui/remote-queue-wiring.ts
|
|
197336
|
-
function createRemoteQueueWiring(deps) {
|
|
197337
|
-
let pendingEdit;
|
|
197338
|
-
return {
|
|
197339
|
-
removeMainQueue(index) {
|
|
197340
|
-
const queue = deps.getQueue();
|
|
197341
|
-
if (index < 0 || index >= queue.length)
|
|
197342
|
-
return;
|
|
197343
|
-
const removed = queue[index];
|
|
197344
|
-
if (removed?.source === TG_SOURCE) {
|
|
197345
|
-
deps.getBridge()?.queuedLineRemoved(removed.question);
|
|
197346
|
-
}
|
|
197347
|
-
deps.setQueue(removeMainQueueItem(queue, index));
|
|
197348
|
-
deps.paint();
|
|
197349
|
-
},
|
|
197350
|
-
editMainQueue(index) {
|
|
197351
|
-
const edited = editMainQueueItem(deps.getQueue(), index);
|
|
197352
|
-
if (edited === undefined)
|
|
197353
|
-
return;
|
|
197354
|
-
deps.setQueue(edited.rest);
|
|
197355
|
-
pendingEdit = pendingQueueEditFor(edited.removed, index);
|
|
197356
|
-
deps.composer.setText(edited.text);
|
|
197357
|
-
deps.composer.focus();
|
|
197358
|
-
deps.paint();
|
|
197359
|
-
},
|
|
197360
|
-
requeuePendingEdit(line, displayLine) {
|
|
197361
|
-
if (pendingEdit === undefined)
|
|
197362
|
-
return false;
|
|
197363
|
-
const edit = pendingEdit;
|
|
197364
|
-
pendingEdit = undefined;
|
|
197365
|
-
deps.setQueue(reinsertEditedMainQueueItem(deps.getQueue(), edit, line, displayLine));
|
|
197366
|
-
deps.paint();
|
|
197367
|
-
return true;
|
|
197368
|
-
},
|
|
197369
|
-
hasPendingEdit: () => pendingEdit !== undefined,
|
|
197370
|
-
stopRemoteForSessionSwitch() {
|
|
197371
|
-
const bridge = deps.getBridge();
|
|
197372
|
-
if (bridge?.keepingTopic === true) {
|
|
197373
|
-
bridge.sessionLeaving();
|
|
197374
|
-
return;
|
|
197375
|
-
}
|
|
197376
|
-
if (bridge?.active !== true)
|
|
197377
|
-
return;
|
|
197378
|
-
const closing = bridge.disable();
|
|
197379
|
-
deps.say(`\u25C7 remote control is turning off: the session changed, and its Telegram topic is being archived. Turn it on again with /remote-control <name>.
|
|
197380
|
-
`);
|
|
197381
|
-
closing.then(() => deps.say(`\u25C7 remote control off: the topic is archived.
|
|
197382
|
-
`));
|
|
197383
|
-
},
|
|
197384
|
-
dropQueuedTelegramLines() {
|
|
197385
|
-
const { kept, dropped } = dropQueuedBySource(deps.getQueue(), TG_SOURCE);
|
|
197386
|
-
if (dropped.length === 0)
|
|
197387
|
-
return [];
|
|
197388
|
-
deps.setQueue(kept);
|
|
197389
|
-
deps.paint();
|
|
197390
|
-
return dropped.map((item) => item.question);
|
|
197391
|
-
},
|
|
197392
|
-
enqueueTelegramLine(text) {
|
|
197393
|
-
const queue = deps.getQueue();
|
|
197394
|
-
queue.push({ id: deps.nextId(), question: text, displayQuestion: deps.summarize(text), source: TG_SOURCE });
|
|
197395
|
-
deps.paint();
|
|
197396
|
-
deps.say(`\u25C7 a line from Telegram is queued as q${queue.length}.
|
|
197397
|
-
`);
|
|
197398
|
-
},
|
|
197399
|
-
runTelegramLine(text) {
|
|
197400
|
-
deps.runLine(text, "operator", TG_SOURCE);
|
|
197401
|
-
},
|
|
197402
|
-
runQueued(item) {
|
|
197403
|
-
deps.runLine(item.question, "operator", item.source);
|
|
197404
|
-
},
|
|
197405
|
-
turnStarted(source) {
|
|
197406
|
-
deps.getBridge()?.turnStarted(source);
|
|
197407
|
-
},
|
|
197408
|
-
assistantText(text) {
|
|
197409
|
-
deps.getBridge()?.assistantText(text);
|
|
197410
|
-
},
|
|
197411
|
-
wrapOnToolCall(base) {
|
|
197412
|
-
return (...args) => {
|
|
197413
|
-
deps.getBridge()?.toolCall(typeof args[0] === "string" ? args[0] : undefined);
|
|
197414
|
-
base?.(...args);
|
|
197415
|
-
};
|
|
197416
|
-
},
|
|
197417
|
-
turnSettled(outcome) {
|
|
197418
|
-
const bridge = deps.getBridge();
|
|
197419
|
-
return bridge === undefined ? Promise.resolve() : bridge.turnSettled({ failed: outcome.failed || outcome.aborted });
|
|
197420
|
-
}
|
|
197421
|
-
};
|
|
197422
|
-
}
|
|
197423
|
-
var init_remote_queue_wiring = __esm(() => {
|
|
197424
|
-
init_shell_bridge();
|
|
197425
|
-
});
|
|
197426
|
-
|
|
197427
|
-
// src/tui/turn-loop.ts
|
|
197428
|
-
function createTurnLoop(deps) {
|
|
197429
|
-
let queue = [];
|
|
197430
|
-
let seq = 0;
|
|
197431
|
-
const nextId = () => `mq${seq++}`;
|
|
197432
|
-
const enqueue = (line, displayLine, source) => {
|
|
197433
|
-
queue.push({ id: nextId(), question: line, displayQuestion: displayLine, ...source !== undefined ? { source } : {} });
|
|
197434
|
-
deps.paint();
|
|
197435
|
-
return queue.length;
|
|
197436
|
-
};
|
|
197437
|
-
const remote = createRemoteQueueWiring({
|
|
197438
|
-
...deps.remote,
|
|
197439
|
-
getQueue: () => queue,
|
|
197440
|
-
setQueue: (next) => {
|
|
197441
|
-
queue = next;
|
|
197442
|
-
},
|
|
197443
|
-
nextId,
|
|
197444
|
-
paint: deps.paint,
|
|
197445
|
-
say: deps.say,
|
|
197446
|
-
runLine: deps.runLine
|
|
197447
|
-
});
|
|
197448
|
-
const drain = createMainQueueDrain({
|
|
197449
|
-
forced: deps.forced,
|
|
197450
|
-
queue: () => queue,
|
|
197451
|
-
paint: deps.paint,
|
|
197452
|
-
run: (next) => remote.runQueued(next),
|
|
197453
|
-
turnWorkInFlight: deps.turnWorkInFlight,
|
|
197454
|
-
onSettle: deps.onSettle,
|
|
197455
|
-
onDetachedFailure: deps.onDetachedFailure
|
|
197456
|
-
});
|
|
197457
|
-
return {
|
|
197458
|
-
get queue() {
|
|
197459
|
-
return queue;
|
|
197460
|
-
},
|
|
197461
|
-
replaceQueue(next) {
|
|
197462
|
-
queue = next;
|
|
197463
|
-
},
|
|
197464
|
-
enqueue,
|
|
197465
|
-
enqueueWhileBusy(line, displayLine, commandName, source) {
|
|
197466
|
-
if (deferredBusyAction(line, commandName) !== "queue")
|
|
197467
|
-
return false;
|
|
197468
|
-
const position = enqueue(line, displayLine, source);
|
|
197469
|
-
deps.say(`${queuedCommandNotice(position)}
|
|
197470
|
-
`);
|
|
197471
|
-
return true;
|
|
197472
|
-
},
|
|
197473
|
-
drainAfterRecipientChoice(isIdle) {
|
|
197474
|
-
drainIdleMainQueue(queue, {
|
|
197475
|
-
isIdle,
|
|
197476
|
-
takeForced: () => deps.forced.takeNext(),
|
|
197477
|
-
dispatch: (next) => {
|
|
197478
|
-
deps.paint();
|
|
197479
|
-
remote.runQueued(next);
|
|
197480
|
-
}
|
|
197481
|
-
});
|
|
197482
|
-
},
|
|
197483
|
-
remote,
|
|
197484
|
-
drain
|
|
197485
|
-
};
|
|
197486
|
-
}
|
|
197487
|
-
var init_turn_loop = __esm(() => {
|
|
197488
|
-
init_busy_dispatch();
|
|
197489
|
-
init_remote_queue_wiring();
|
|
197490
|
-
});
|
|
197491
|
-
|
|
197492
197602
|
// src/tui/worker-fleet.ts
|
|
197493
197603
|
function clip8(s, max) {
|
|
197494
197604
|
if (s.length <= max) {
|
|
@@ -198084,6 +198194,818 @@ var init_subagent_inspector = __esm(() => {
|
|
|
198084
198194
|
];
|
|
198085
198195
|
});
|
|
198086
198196
|
|
|
198197
|
+
// src/tui/background-job-session.ts
|
|
198198
|
+
function appendBounded(output, chunk) {
|
|
198199
|
+
const combined = output + chunk;
|
|
198200
|
+
return combined.length > MAX_BACKGROUND_JOB_OUTPUT_CHARS ? combined.slice(-MAX_BACKGROUND_JOB_OUTPUT_CHARS) : combined;
|
|
198201
|
+
}
|
|
198202
|
+
function clip10(s, max) {
|
|
198203
|
+
if (max <= 0) {
|
|
198204
|
+
return "";
|
|
198205
|
+
}
|
|
198206
|
+
if (s.length <= max) {
|
|
198207
|
+
return s;
|
|
198208
|
+
}
|
|
198209
|
+
return `${s.slice(0, Math.max(1, max - 1))}\u2026`;
|
|
198210
|
+
}
|
|
198211
|
+
function formatJobListHeader(count) {
|
|
198212
|
+
return `Background Jobs ${count}`;
|
|
198213
|
+
}
|
|
198214
|
+
function formatJobRow(entry, width = SIDEBAR_TEXT_WIDTH) {
|
|
198215
|
+
const glyph = STATUS_GLYPH4[entry.status];
|
|
198216
|
+
const budget = Math.max(1, width - glyph.length - 1);
|
|
198217
|
+
const cmd = clip10(entry.command, budget);
|
|
198218
|
+
return clip10(`${glyph} ${cmd}`, width);
|
|
198219
|
+
}
|
|
198220
|
+
function formatJobDuration(entry, now) {
|
|
198221
|
+
const started = Date.parse(entry.startedAt);
|
|
198222
|
+
const ended = entry.endedAt !== undefined ? Date.parse(entry.endedAt) : now;
|
|
198223
|
+
return formatDuration(ended - started);
|
|
198224
|
+
}
|
|
198225
|
+
function formatJobMeta(entry, now = Date.now()) {
|
|
198226
|
+
const rows = [
|
|
198227
|
+
["Id", entry.jobId],
|
|
198228
|
+
["Pid", String(entry.pid)],
|
|
198229
|
+
["Status", entry.status],
|
|
198230
|
+
["Command", entry.command],
|
|
198231
|
+
["Started", entry.startedAt],
|
|
198232
|
+
["Ended", entry.endedAt ?? "\u2014"],
|
|
198233
|
+
["Duration", formatJobDuration(entry, now)],
|
|
198234
|
+
["Exit code", entry.exitCode !== undefined ? String(entry.exitCode) : "\u2014"]
|
|
198235
|
+
];
|
|
198236
|
+
if (entry.killReason !== undefined) {
|
|
198237
|
+
rows.push(["Kill reason", entry.killReason]);
|
|
198238
|
+
}
|
|
198239
|
+
const width = rows.reduce((max, [label]) => Math.max(max, label.length), 0);
|
|
198240
|
+
return rows.map(([label, value]) => `${label.padEnd(width)} ${value}`).join(`
|
|
198241
|
+
`);
|
|
198242
|
+
}
|
|
198243
|
+
function formatJobOutput(entry) {
|
|
198244
|
+
return entry.output.length > 0 ? entry.output : "(no output yet)";
|
|
198245
|
+
}
|
|
198246
|
+
|
|
198247
|
+
class BackgroundJobStore {
|
|
198248
|
+
jobs = new Map;
|
|
198249
|
+
pending = new Map;
|
|
198250
|
+
listeners = new Set;
|
|
198251
|
+
apply(event) {
|
|
198252
|
+
switch (event.type) {
|
|
198253
|
+
case "start":
|
|
198254
|
+
this.pending.set(event.jobId, {
|
|
198255
|
+
jobId: event.jobId,
|
|
198256
|
+
command: event.command,
|
|
198257
|
+
pid: event.pid,
|
|
198258
|
+
status: "running",
|
|
198259
|
+
startedAt: event.startedAt,
|
|
198260
|
+
output: ""
|
|
198261
|
+
});
|
|
198262
|
+
return;
|
|
198263
|
+
case "phase": {
|
|
198264
|
+
const entry = this.pending.get(event.jobId);
|
|
198265
|
+
if (entry === undefined) {
|
|
198266
|
+
return;
|
|
198267
|
+
}
|
|
198268
|
+
this.pending.delete(event.jobId);
|
|
198269
|
+
this.jobs.set(event.jobId, entry);
|
|
198270
|
+
this.emit({ id: event.jobId, kind: "start" });
|
|
198271
|
+
return;
|
|
198272
|
+
}
|
|
198273
|
+
case "output": {
|
|
198274
|
+
const hidden = this.pending.get(event.jobId);
|
|
198275
|
+
if (hidden !== undefined) {
|
|
198276
|
+
hidden.output = appendBounded(hidden.output, event.chunk);
|
|
198277
|
+
return;
|
|
198278
|
+
}
|
|
198279
|
+
const current = this.jobs.get(event.jobId);
|
|
198280
|
+
if (current === undefined) {
|
|
198281
|
+
return;
|
|
198282
|
+
}
|
|
198283
|
+
current.output = appendBounded(current.output, event.chunk);
|
|
198284
|
+
this.emit({ id: event.jobId, kind: "output" });
|
|
198285
|
+
return;
|
|
198286
|
+
}
|
|
198287
|
+
case "exit": {
|
|
198288
|
+
if (this.pending.delete(event.jobId)) {
|
|
198289
|
+
return;
|
|
198290
|
+
}
|
|
198291
|
+
const current = this.jobs.get(event.jobId);
|
|
198292
|
+
if (current === undefined) {
|
|
198293
|
+
return;
|
|
198294
|
+
}
|
|
198295
|
+
current.status = event.status;
|
|
198296
|
+
current.endedAt = event.endedAt;
|
|
198297
|
+
if (event.exitCode !== undefined) {
|
|
198298
|
+
current.exitCode = event.exitCode;
|
|
198299
|
+
}
|
|
198300
|
+
if (event.killReason !== undefined) {
|
|
198301
|
+
current.killReason = event.killReason;
|
|
198302
|
+
}
|
|
198303
|
+
this.emit({ id: event.jobId, kind: "exit" });
|
|
198304
|
+
return;
|
|
198305
|
+
}
|
|
198306
|
+
}
|
|
198307
|
+
}
|
|
198308
|
+
get(jobId) {
|
|
198309
|
+
return this.jobs.get(jobId);
|
|
198310
|
+
}
|
|
198311
|
+
list() {
|
|
198312
|
+
return [...this.jobs.values()];
|
|
198313
|
+
}
|
|
198314
|
+
subscribe(listener) {
|
|
198315
|
+
this.listeners.add(listener);
|
|
198316
|
+
return () => {
|
|
198317
|
+
this.listeners.delete(listener);
|
|
198318
|
+
};
|
|
198319
|
+
}
|
|
198320
|
+
removeAll() {
|
|
198321
|
+
this.pending.clear();
|
|
198322
|
+
if (this.jobs.size === 0) {
|
|
198323
|
+
return;
|
|
198324
|
+
}
|
|
198325
|
+
const ids = [...this.jobs.keys()];
|
|
198326
|
+
this.jobs.clear();
|
|
198327
|
+
for (const id of ids) {
|
|
198328
|
+
this.emit({ id, kind: "exit" });
|
|
198329
|
+
}
|
|
198330
|
+
}
|
|
198331
|
+
emit(hint) {
|
|
198332
|
+
for (const listener of this.listeners) {
|
|
198333
|
+
try {
|
|
198334
|
+
listener(hint);
|
|
198335
|
+
} catch {}
|
|
198336
|
+
}
|
|
198337
|
+
}
|
|
198338
|
+
}
|
|
198339
|
+
var MAX_BACKGROUND_JOB_OUTPUT_CHARS = 20000, STATUS_GLYPH4;
|
|
198340
|
+
var init_background_job_session = __esm(() => {
|
|
198341
|
+
init_shell_chrome();
|
|
198342
|
+
init_duration_format();
|
|
198343
|
+
STATUS_GLYPH4 = {
|
|
198344
|
+
running: "\u25D0",
|
|
198345
|
+
completed: "\u25CF",
|
|
198346
|
+
failed: "\u25B2",
|
|
198347
|
+
killed: "\u2717"
|
|
198348
|
+
};
|
|
198349
|
+
});
|
|
198350
|
+
|
|
198351
|
+
// src/tui/background-job-inspector.ts
|
|
198352
|
+
function resolveTarget(otui, renderer, body) {
|
|
198353
|
+
if (otui === undefined || otui === null || body === undefined || body === null) {
|
|
198354
|
+
return;
|
|
198355
|
+
}
|
|
198356
|
+
const parent = body;
|
|
198357
|
+
if (parent.add === undefined) {
|
|
198358
|
+
return;
|
|
198359
|
+
}
|
|
198360
|
+
const scrollCtor = otui.ScrollBoxRenderable;
|
|
198361
|
+
if (scrollCtor === undefined) {
|
|
198362
|
+
return { add: parent.add.bind(parent) };
|
|
198363
|
+
}
|
|
198364
|
+
const scroll = new scrollCtor(renderer, {
|
|
198365
|
+
id: "job-inspector-scroll",
|
|
198366
|
+
flexGrow: 1,
|
|
198367
|
+
minHeight: 0,
|
|
198368
|
+
scrollY: true,
|
|
198369
|
+
stickyScroll: true,
|
|
198370
|
+
stickyStart: "bottom",
|
|
198371
|
+
contentOptions: { flexDirection: "column" }
|
|
198372
|
+
});
|
|
198373
|
+
parent.add(scroll);
|
|
198374
|
+
const target = scroll.content ?? scroll;
|
|
198375
|
+
if (target.add === undefined) {
|
|
198376
|
+
return;
|
|
198377
|
+
}
|
|
198378
|
+
return { add: target.add.bind(target) };
|
|
198379
|
+
}
|
|
198380
|
+
function paintContent3(otui, renderer, body, content) {
|
|
198381
|
+
const target = resolveTarget(otui, renderer, body);
|
|
198382
|
+
const textCtor = otui.TextRenderable;
|
|
198383
|
+
if (target === undefined || textCtor === undefined) {
|
|
198384
|
+
return;
|
|
198385
|
+
}
|
|
198386
|
+
const node = new textCtor(renderer, { id: "job-inspector-output", content });
|
|
198387
|
+
target.add(node);
|
|
198388
|
+
return node;
|
|
198389
|
+
}
|
|
198390
|
+
function paintMetaTab(otui, renderer, body, entry, registry, jobId) {
|
|
198391
|
+
const target = resolveTarget(otui, renderer, body);
|
|
198392
|
+
const textCtor = otui.TextRenderable;
|
|
198393
|
+
if (target === undefined || textCtor === undefined) {
|
|
198394
|
+
return;
|
|
198395
|
+
}
|
|
198396
|
+
const metaNode = new textCtor(renderer, { id: "job-inspector-meta", content: formatJobMeta(entry) });
|
|
198397
|
+
target.add(metaNode);
|
|
198398
|
+
const killStatusNode = new textCtor(renderer, { id: "job-inspector-kill-status", content: "" });
|
|
198399
|
+
const killNode = new textCtor(renderer, {
|
|
198400
|
+
id: "job-inspector-kill",
|
|
198401
|
+
content: "[ Kill ]",
|
|
198402
|
+
onMouseDown: () => {
|
|
198403
|
+
registry.kill(jobId).then((result) => {
|
|
198404
|
+
if (!result.ok) {
|
|
198405
|
+
killStatusNode.content = `Kill failed: ${result.error}`;
|
|
198406
|
+
}
|
|
198407
|
+
});
|
|
198408
|
+
}
|
|
198409
|
+
});
|
|
198410
|
+
target.add(killNode);
|
|
198411
|
+
target.add(killStatusNode);
|
|
198412
|
+
return metaNode;
|
|
198413
|
+
}
|
|
198414
|
+
function inspectorTitleForJob(entry) {
|
|
198415
|
+
return entry.command;
|
|
198416
|
+
}
|
|
198417
|
+
function presentJobInspector(openModalFn, otui, chrome, options) {
|
|
198418
|
+
const entry = options.store.get(options.id);
|
|
198419
|
+
if (entry === undefined) {
|
|
198420
|
+
return;
|
|
198421
|
+
}
|
|
198422
|
+
let outputNode;
|
|
198423
|
+
let metaNode;
|
|
198424
|
+
let unsubscribe;
|
|
198425
|
+
const refresh = (hint) => {
|
|
198426
|
+
if (hint !== undefined && hint.id !== options.id) {
|
|
198427
|
+
return;
|
|
198428
|
+
}
|
|
198429
|
+
const current = options.store.get(options.id);
|
|
198430
|
+
if (current === undefined) {
|
|
198431
|
+
if (outputNode !== undefined) {
|
|
198432
|
+
outputNode.content = GONE_TEXT;
|
|
198433
|
+
}
|
|
198434
|
+
if (metaNode !== undefined) {
|
|
198435
|
+
metaNode.content = GONE_TEXT;
|
|
198436
|
+
}
|
|
198437
|
+
return;
|
|
198438
|
+
}
|
|
198439
|
+
if (outputNode !== undefined) {
|
|
198440
|
+
outputNode.content = formatJobOutput(current);
|
|
198441
|
+
}
|
|
198442
|
+
if (metaNode !== undefined) {
|
|
198443
|
+
metaNode.content = formatJobMeta(current);
|
|
198444
|
+
}
|
|
198445
|
+
};
|
|
198446
|
+
const handle = openModalFn(otui, chrome, {
|
|
198447
|
+
title: inspectorTitleForJob(entry),
|
|
198448
|
+
tabs: [
|
|
198449
|
+
{ id: "output", label: "Output" },
|
|
198450
|
+
{ id: "meta", label: "Meta" }
|
|
198451
|
+
],
|
|
198452
|
+
initialTab: "output",
|
|
198453
|
+
footer: JOB_INSPECTOR_FOOTER,
|
|
198454
|
+
renderTab: (tabId, body) => {
|
|
198455
|
+
const renderer = options.renderer ?? chrome?.renderer;
|
|
198456
|
+
const current = options.store.get(options.id);
|
|
198457
|
+
if (current === undefined) {
|
|
198458
|
+
paintContent3(otui, renderer, body, GONE_TEXT);
|
|
198459
|
+
return;
|
|
198460
|
+
}
|
|
198461
|
+
if (tabId === "meta") {
|
|
198462
|
+
metaNode = paintMetaTab(otui, renderer, body, current, options.registry, options.id);
|
|
198463
|
+
outputNode = undefined;
|
|
198464
|
+
return;
|
|
198465
|
+
}
|
|
198466
|
+
outputNode = paintContent3(otui, renderer, body, formatJobOutput(current));
|
|
198467
|
+
metaNode = undefined;
|
|
198468
|
+
},
|
|
198469
|
+
onClose: () => {
|
|
198470
|
+
unsubscribe?.();
|
|
198471
|
+
}
|
|
198472
|
+
});
|
|
198473
|
+
if (handle === undefined) {
|
|
198474
|
+
return;
|
|
198475
|
+
}
|
|
198476
|
+
unsubscribe = options.store.subscribe(refresh);
|
|
198477
|
+
return handle;
|
|
198478
|
+
}
|
|
198479
|
+
function openJobInspector(otui, chrome, options) {
|
|
198480
|
+
return presentJobInspector((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, options);
|
|
198481
|
+
}
|
|
198482
|
+
function paintBackgroundJobSidebar(otui, renderer, parent, entries, options) {
|
|
198483
|
+
const box = parent;
|
|
198484
|
+
const ctor = otui.TextRenderable;
|
|
198485
|
+
if (box.add === undefined || ctor === undefined) {
|
|
198486
|
+
return;
|
|
198487
|
+
}
|
|
198488
|
+
if (box.getChildren !== undefined && box.remove !== undefined) {
|
|
198489
|
+
for (const child of [...box.getChildren()]) {
|
|
198490
|
+
box.remove(child);
|
|
198491
|
+
}
|
|
198492
|
+
}
|
|
198493
|
+
if (entries.length === 0) {
|
|
198494
|
+
return;
|
|
198495
|
+
}
|
|
198496
|
+
const width = options.width ?? SIDEBAR_TEXT_WIDTH;
|
|
198497
|
+
box.add(new ctor(renderer, {
|
|
198498
|
+
id: "sb-jobs-h",
|
|
198499
|
+
content: formatJobListHeader(entries.length)
|
|
198500
|
+
}));
|
|
198501
|
+
for (const entry of entries) {
|
|
198502
|
+
const id = entry.jobId;
|
|
198503
|
+
box.add(new ctor(renderer, {
|
|
198504
|
+
id: `sb-job-${id}`,
|
|
198505
|
+
content: formatJobRow(entry, width),
|
|
198506
|
+
onMouseDown: () => {
|
|
198507
|
+
options.onOpen(id);
|
|
198508
|
+
}
|
|
198509
|
+
}));
|
|
198510
|
+
}
|
|
198511
|
+
}
|
|
198512
|
+
var JOB_INSPECTOR_FOOTER, GONE_TEXT = "Background job is gone.";
|
|
198513
|
+
var init_background_job_inspector = __esm(() => {
|
|
198514
|
+
init_modal_host();
|
|
198515
|
+
init_shell_chrome();
|
|
198516
|
+
init_background_job_session();
|
|
198517
|
+
JOB_INSPECTOR_FOOTER = [
|
|
198518
|
+
{ key: "\u2190/\u2192", label: "tabs" },
|
|
198519
|
+
{ key: "k", label: "kill" },
|
|
198520
|
+
{ key: "esc", label: "close" }
|
|
198521
|
+
];
|
|
198522
|
+
});
|
|
198523
|
+
|
|
198524
|
+
// src/tui/sidebar-work.ts
|
|
198525
|
+
function counts2(label, statuses, done) {
|
|
198526
|
+
const n = (status) => statuses.filter((value) => value === status).length;
|
|
198527
|
+
const failures = n("failed") + n("killed");
|
|
198528
|
+
return `${label} ${n("running")}/${n(done)}` + (n("queued") > 0 ? ` \xB7 ${n("queued")} queued` : "") + (n("blocked") > 0 ? ` \xB7 ${n("blocked")} blocked` : "") + (failures > 0 ? ` \xB7 ${failures} failed` : "");
|
|
198529
|
+
}
|
|
198530
|
+
function workSidebarSnapshot(agents, jobs) {
|
|
198531
|
+
return {
|
|
198532
|
+
sections: {
|
|
198533
|
+
...agents.length > 0 ? { subagents: counts2("Subagents", agents.map((a) => a.status), "done") } : {},
|
|
198534
|
+
...jobs.length > 0 ? { jobs: counts2("Jobs", jobs.map((j) => j.status), "completed") } : {}
|
|
198535
|
+
},
|
|
198536
|
+
items: {
|
|
198537
|
+
subagents: agents.map((a) => ({ id: a.id, label: a.label, text: `${a.status} \xB7 ${a.label}` })),
|
|
198538
|
+
jobs: jobs.map((j) => ({ id: j.jobId, label: j.command, text: `${j.status} \xB7 ${j.command}` }))
|
|
198539
|
+
}
|
|
198540
|
+
};
|
|
198541
|
+
}
|
|
198542
|
+
function embeddedInspector(otui, chrome, body) {
|
|
198543
|
+
let dispose = () => {};
|
|
198544
|
+
const open = (_otui, _chrome, input) => {
|
|
198545
|
+
const parent = body;
|
|
198546
|
+
const nav = new otui.BoxRenderable(chrome.renderer, { id: "embedded-inspector-tabs", flexDirection: "row", flexShrink: 0 });
|
|
198547
|
+
const content = new otui.BoxRenderable(chrome.renderer, { id: "embedded-inspector-content", flexDirection: "column", flexGrow: 1, minHeight: 0 });
|
|
198548
|
+
parent.add(nav);
|
|
198549
|
+
parent.add(content);
|
|
198550
|
+
let active = input.initialTab ?? input.tabs[0]?.id ?? "";
|
|
198551
|
+
let cleanup;
|
|
198552
|
+
const buttons = new Map;
|
|
198553
|
+
const mount = (id) => {
|
|
198554
|
+
if (!input.tabs.some((tab) => tab.id === id))
|
|
198555
|
+
return;
|
|
198556
|
+
cleanup?.();
|
|
198557
|
+
cleanup = undefined;
|
|
198558
|
+
for (const child of [...content.getChildren()]) {
|
|
198559
|
+
content.remove(child);
|
|
198560
|
+
child.destroy();
|
|
198561
|
+
}
|
|
198562
|
+
active = id;
|
|
198563
|
+
for (const [tabId, button] of buttons)
|
|
198564
|
+
button.fg = roleColor(tabId === id ? "text" : "muted");
|
|
198565
|
+
const result = input.renderTab(id, content);
|
|
198566
|
+
if (typeof result === "function")
|
|
198567
|
+
cleanup = result;
|
|
198568
|
+
chrome.renderer.requestRender();
|
|
198569
|
+
};
|
|
198570
|
+
for (const tab of input.tabs) {
|
|
198571
|
+
const button = new otui.TextRenderable(chrome.renderer, {
|
|
198572
|
+
id: `embedded-inspector-tab-${tab.id}`,
|
|
198573
|
+
content: `[ ${tab.label} ] `,
|
|
198574
|
+
onMouseDown: () => mount(tab.id)
|
|
198575
|
+
});
|
|
198576
|
+
buttons.set(tab.id, button);
|
|
198577
|
+
nav.add(button);
|
|
198578
|
+
}
|
|
198579
|
+
mount(active);
|
|
198580
|
+
let closed = false;
|
|
198581
|
+
dispose = () => {
|
|
198582
|
+
if (closed)
|
|
198583
|
+
return;
|
|
198584
|
+
closed = true;
|
|
198585
|
+
cleanup?.();
|
|
198586
|
+
input.onClose?.();
|
|
198587
|
+
};
|
|
198588
|
+
return { close: () => dispose(), setTab: mount, activeTab: () => active };
|
|
198589
|
+
};
|
|
198590
|
+
return { open, dispose: () => dispose() };
|
|
198591
|
+
}
|
|
198592
|
+
function renderWorkInspector(otui, chrome, body, section, id, options) {
|
|
198593
|
+
const embedded = embeddedInspector(otui, chrome, body);
|
|
198594
|
+
const handle = section === "subagents" ? presentSubagentInspector(embedded.open, otui, chrome, { store: options.agents, id, renderer: chrome.renderer }) : presentJobInspector(embedded.open, otui, chrome, { store: options.jobs, id, registry: options.registry, renderer: chrome.renderer });
|
|
198595
|
+
if (handle === undefined) {
|
|
198596
|
+
body.add(new otui.TextRenderable(chrome.renderer, {
|
|
198597
|
+
id: "embedded-inspector-gone",
|
|
198598
|
+
content: "This worker is gone.",
|
|
198599
|
+
fg: roleColor("muted")
|
|
198600
|
+
}));
|
|
198601
|
+
}
|
|
198602
|
+
return embedded.dispose;
|
|
198603
|
+
}
|
|
198604
|
+
var init_sidebar_work = __esm(() => {
|
|
198605
|
+
init_subagent_inspector();
|
|
198606
|
+
init_background_job_inspector();
|
|
198607
|
+
init_theme();
|
|
198608
|
+
});
|
|
198609
|
+
|
|
198610
|
+
// src/tui/side-worker.ts
|
|
198611
|
+
function isSideWorkerId(id) {
|
|
198612
|
+
return id.startsWith(SIDE_WORKER_ID_PREFIX);
|
|
198613
|
+
}
|
|
198614
|
+
function buildSideWorkerPrompt(opts) {
|
|
198615
|
+
const maxRecent = opts.maxRecent ?? 10;
|
|
198616
|
+
const recent = opts.recentHistory.slice(-maxRecent);
|
|
198617
|
+
const lines = [
|
|
198618
|
+
"## Live main-agent state (read-only snapshot)",
|
|
198619
|
+
`phase: ${opts.snapshot.phase || "unknown"}`
|
|
198620
|
+
];
|
|
198621
|
+
if (opts.snapshot.mainDetail !== undefined && opts.snapshot.mainDetail.length > 0) {
|
|
198622
|
+
lines.push(`detail: ${opts.snapshot.mainDetail}`);
|
|
198623
|
+
}
|
|
198624
|
+
if (opts.snapshot.elapsedSec !== undefined) {
|
|
198625
|
+
lines.push(`elapsed: ${opts.snapshot.elapsedSec.toFixed(1)}s`);
|
|
198626
|
+
}
|
|
198627
|
+
lines.push("", "## Recent main conversation (truncated; for context only)");
|
|
198628
|
+
if (recent.length === 0) {
|
|
198629
|
+
lines.push("(empty)");
|
|
198630
|
+
} else {
|
|
198631
|
+
for (const m of recent) {
|
|
198632
|
+
const body = m.content.length > 400 ? `${m.content.slice(0, 397)}\u2026` : m.content;
|
|
198633
|
+
lines.push(`### ${m.role}`, body, "");
|
|
198634
|
+
}
|
|
198635
|
+
}
|
|
198636
|
+
lines.push("## Your task", "Answer the user's SIDE question below. Do NOT continue or take over the main task.", "Be brief. Prefer facts already visible above; use read-only tools only if essential.", "If the question is about what the main agent is doing, use the live snapshot.", "", "## Side question", opts.question.trim());
|
|
198637
|
+
return lines.join(`
|
|
198638
|
+
`);
|
|
198639
|
+
}
|
|
198640
|
+
function buildSideWorkerSystemInstruction(providerId, modelId) {
|
|
198641
|
+
return `You are a keryx SIDE worker answering a user question while the MAIN agent is busy.
|
|
198642
|
+
` + `Rules:
|
|
198643
|
+
` + `- Answer ONLY the side question; do not continue the main task.
|
|
198644
|
+
` + `- Do not call shell_exec or any mutating tool. Read-only tools only if needed.
|
|
198645
|
+
` + `- Be concise (prefer bullets). Lead with the answer.
|
|
198646
|
+
` + `- Provider/model: ${providerId}/${modelId}.
|
|
198647
|
+
`;
|
|
198648
|
+
}
|
|
198649
|
+
function sideWorkerLabel(seq) {
|
|
198650
|
+
return `side-${seq}`;
|
|
198651
|
+
}
|
|
198652
|
+
var SIDE_WORKER_ID_PREFIX = "side:";
|
|
198653
|
+
|
|
198654
|
+
// src/tui/main-queue.ts
|
|
198655
|
+
function parseQueueCommand(args) {
|
|
198656
|
+
const parts = args.trim().split(/\s+/).filter((part) => part.length > 0);
|
|
198657
|
+
const action = parts[0]?.toLowerCase();
|
|
198658
|
+
if (action !== "remove" && action !== "edit" && action !== "force") {
|
|
198659
|
+
return;
|
|
198660
|
+
}
|
|
198661
|
+
const raw = parts[1];
|
|
198662
|
+
if (raw === undefined) {
|
|
198663
|
+
return { action, position: 1 };
|
|
198664
|
+
}
|
|
198665
|
+
if (!/^\d+$/.test(raw)) {
|
|
198666
|
+
return;
|
|
198667
|
+
}
|
|
198668
|
+
const position = Number.parseInt(raw, 10);
|
|
198669
|
+
if (position < 1) {
|
|
198670
|
+
return;
|
|
198671
|
+
}
|
|
198672
|
+
return { action, position };
|
|
198673
|
+
}
|
|
198674
|
+
function formatMainQueueMarker(index) {
|
|
198675
|
+
const n = index + 1;
|
|
198676
|
+
return `> q${n} (${n})`;
|
|
198677
|
+
}
|
|
198678
|
+
function removeMainQueueItem(items, index) {
|
|
198679
|
+
if (index < 0 || index >= items.length)
|
|
198680
|
+
return [...items];
|
|
198681
|
+
const next = [...items];
|
|
198682
|
+
next.splice(index, 1);
|
|
198683
|
+
return next;
|
|
198684
|
+
}
|
|
198685
|
+
function editMainQueueItem(items, index) {
|
|
198686
|
+
if (index < 0 || index >= items.length)
|
|
198687
|
+
return;
|
|
198688
|
+
const rest = [...items];
|
|
198689
|
+
const removed = rest.splice(index, 1)[0];
|
|
198690
|
+
return { text: removed.question, rest, removed };
|
|
198691
|
+
}
|
|
198692
|
+
function reinsertMainQueueItem(items, at, item) {
|
|
198693
|
+
const next = [...items];
|
|
198694
|
+
const insertAt = Math.max(0, Math.min(at, next.length));
|
|
198695
|
+
next.splice(insertAt, 0, item);
|
|
198696
|
+
return next;
|
|
198697
|
+
}
|
|
198698
|
+
function pendingQueueEditFor(removed, at) {
|
|
198699
|
+
return { id: removed.id, at, ...removed.source !== undefined ? { source: removed.source } : {} };
|
|
198700
|
+
}
|
|
198701
|
+
function reinsertEditedMainQueueItem(items, edit, question, displayQuestion) {
|
|
198702
|
+
return reinsertMainQueueItem(items, edit.at, {
|
|
198703
|
+
id: edit.id,
|
|
198704
|
+
question,
|
|
198705
|
+
displayQuestion,
|
|
198706
|
+
...edit.source !== undefined ? { source: edit.source } : {}
|
|
198707
|
+
});
|
|
198708
|
+
}
|
|
198709
|
+
function dropQueuedBySource(items, source) {
|
|
198710
|
+
const kept = [];
|
|
198711
|
+
const dropped = [];
|
|
198712
|
+
for (const item of items) {
|
|
198713
|
+
(item.source === source ? dropped : kept).push(item);
|
|
198714
|
+
}
|
|
198715
|
+
return { kept, dropped };
|
|
198716
|
+
}
|
|
198717
|
+
function drainIdleMainQueue(items, options) {
|
|
198718
|
+
if (!options.isIdle())
|
|
198719
|
+
return false;
|
|
198720
|
+
const next = options.takeForced() ?? items.shift();
|
|
198721
|
+
if (next === undefined)
|
|
198722
|
+
return false;
|
|
198723
|
+
options.dispatch(next);
|
|
198724
|
+
return true;
|
|
198725
|
+
}
|
|
198726
|
+
function drainNext(options) {
|
|
198727
|
+
for (let budget = options.pending();budget > 0; budget--) {
|
|
198728
|
+
const before = options.pending();
|
|
198729
|
+
const next = options.take();
|
|
198730
|
+
options.paint();
|
|
198731
|
+
if (next === undefined)
|
|
198732
|
+
return "empty";
|
|
198733
|
+
options.run(next);
|
|
198734
|
+
if (options.isBusy())
|
|
198735
|
+
return "started";
|
|
198736
|
+
if (options.pending() >= before)
|
|
198737
|
+
return "exhausted";
|
|
198738
|
+
}
|
|
198739
|
+
return "empty";
|
|
198740
|
+
}
|
|
198741
|
+
function createMainQueueDrain(deps) {
|
|
198742
|
+
let detached = 0;
|
|
198743
|
+
const workInFlight = () => deps.turnWorkInFlight() || detached > 0;
|
|
198744
|
+
const drain = () => {
|
|
198745
|
+
const outcome = drainNext({
|
|
198746
|
+
take: () => deps.forced.takeNext() ?? deps.queue().shift(),
|
|
198747
|
+
pending: () => deps.forced.pendingCount + deps.queue().length,
|
|
198748
|
+
paint: deps.paint,
|
|
198749
|
+
run: deps.run,
|
|
198750
|
+
isBusy: workInFlight
|
|
198751
|
+
});
|
|
198752
|
+
if (outcome === "empty")
|
|
198753
|
+
deps.onSettle();
|
|
198754
|
+
return outcome;
|
|
198755
|
+
};
|
|
198756
|
+
const drainWhenIdle = () => {
|
|
198757
|
+
if (workInFlight())
|
|
198758
|
+
return;
|
|
198759
|
+
drain();
|
|
198760
|
+
};
|
|
198761
|
+
return {
|
|
198762
|
+
onForegroundOperationComplete() {
|
|
198763
|
+
if (deps.forced.isAwaitingSettlement)
|
|
198764
|
+
return;
|
|
198765
|
+
drain();
|
|
198766
|
+
},
|
|
198767
|
+
onTurnSettle() {
|
|
198768
|
+
if (deps.forced.isAwaitingSettlement)
|
|
198769
|
+
return;
|
|
198770
|
+
drain();
|
|
198771
|
+
},
|
|
198772
|
+
onLeaseRelease: drainWhenIdle,
|
|
198773
|
+
detach(work) {
|
|
198774
|
+
detached += 1;
|
|
198775
|
+
const end = () => {
|
|
198776
|
+
detached -= 1;
|
|
198777
|
+
drainWhenIdle();
|
|
198778
|
+
};
|
|
198779
|
+
work.then(end, (error) => {
|
|
198780
|
+
try {
|
|
198781
|
+
deps.onDetachedFailure(error);
|
|
198782
|
+
} catch {}
|
|
198783
|
+
end();
|
|
198784
|
+
});
|
|
198785
|
+
},
|
|
198786
|
+
workInFlight
|
|
198787
|
+
};
|
|
198788
|
+
}
|
|
198789
|
+
function describePendingCall(tool, inputJson) {
|
|
198790
|
+
let shown = tool;
|
|
198791
|
+
try {
|
|
198792
|
+
const parsed = JSON.parse(inputJson);
|
|
198793
|
+
if (parsed !== null && typeof parsed === "object") {
|
|
198794
|
+
const command = parsed.command;
|
|
198795
|
+
if (typeof command === "string" && command.trim().length > 0)
|
|
198796
|
+
shown = command;
|
|
198797
|
+
}
|
|
198798
|
+
} catch {}
|
|
198799
|
+
const oneLine = shown.replace(/\s+/g, " ").trim();
|
|
198800
|
+
return oneLine.length > PENDING_CALL_CHARS ? `${oneLine.slice(0, PENDING_CALL_CHARS - 1)}\u2026` : oneLine;
|
|
198801
|
+
}
|
|
198802
|
+
function queueWaitReason(wait) {
|
|
198803
|
+
return wait.kind === "turn" ? "waits for the current turn" : `waits for approval of: ${wait.call}`;
|
|
198804
|
+
}
|
|
198805
|
+
function formatQueueRow(index, text, wait) {
|
|
198806
|
+
return `${formatMainQueueMarker(index)} ${text} \u2014 queued \xB7 ${queueWaitReason(wait)}`;
|
|
198807
|
+
}
|
|
198808
|
+
function formatQueueListing(texts, wait) {
|
|
198809
|
+
if (texts.length === 0)
|
|
198810
|
+
return ["main queue is empty."];
|
|
198811
|
+
return [...texts.map((text, index) => formatQueueRow(index, text, wait)), QUEUE_FORCE_HELP];
|
|
198812
|
+
}
|
|
198813
|
+
var QUEUE_FORCE_HELP = "[Force] interrupts the current turn and runs this message now \xB7 [Edit] moves it back to the composer \xB7 [Delete] removes it", PENDING_CALL_CHARS = 60;
|
|
198814
|
+
|
|
198815
|
+
// src/tui/main-queue-view.ts
|
|
198816
|
+
function buildMainQueueRow(otui, r, params) {
|
|
198817
|
+
const theme = getTheme();
|
|
198818
|
+
const row = new otui.BoxRenderable(r, { id: `mq-${params.id}`, width: "100%", flexDirection: "row" });
|
|
198819
|
+
row.add(new otui.TextRenderable(r, {
|
|
198820
|
+
id: `mq-${params.id}-t`,
|
|
198821
|
+
flexGrow: 1,
|
|
198822
|
+
minWidth: 0,
|
|
198823
|
+
content: otui.t`${dimChunk(otui, formatQueueRow(params.index, params.text, params.wait))}`
|
|
198824
|
+
}));
|
|
198825
|
+
const force = smallActionButton(otui, r, "Force", `mq-force-${params.id}`, theme.focus, params.onForce);
|
|
198826
|
+
const edit = smallActionButton(otui, r, "Edit", `mq-edit-${params.id}`, theme.text, params.onEdit);
|
|
198827
|
+
const del = smallActionButton(otui, r, "Delete", `mq-del-${params.id}`, theme.error, params.onDelete);
|
|
198828
|
+
row.add(force.box);
|
|
198829
|
+
row.add(edit.box);
|
|
198830
|
+
row.add(del.box);
|
|
198831
|
+
return {
|
|
198832
|
+
box: row,
|
|
198833
|
+
buttons: { force, edit, delete: del },
|
|
198834
|
+
setRowActive: (active) => {
|
|
198835
|
+
row.backgroundColor = active ? theme.highlight : undefined;
|
|
198836
|
+
}
|
|
198837
|
+
};
|
|
198838
|
+
}
|
|
198839
|
+
function buildMainQueueHelp(otui, r) {
|
|
198840
|
+
return new otui.TextRenderable(r, {
|
|
198841
|
+
id: "mq-help",
|
|
198842
|
+
width: "100%",
|
|
198843
|
+
content: otui.t`${dimChunk(otui, QUEUE_FORCE_HELP)}`
|
|
198844
|
+
});
|
|
198845
|
+
}
|
|
198846
|
+
var init_main_queue_view = __esm(() => {
|
|
198847
|
+
init_action_button();
|
|
198848
|
+
init_theme();
|
|
198849
|
+
init_theme_text();
|
|
198850
|
+
});
|
|
198851
|
+
|
|
198852
|
+
// src/tui/remote-queue-wiring.ts
|
|
198853
|
+
function createRemoteQueueWiring(deps) {
|
|
198854
|
+
let pendingEdit;
|
|
198855
|
+
return {
|
|
198856
|
+
removeMainQueue(index) {
|
|
198857
|
+
const queue = deps.getQueue();
|
|
198858
|
+
if (index < 0 || index >= queue.length)
|
|
198859
|
+
return;
|
|
198860
|
+
const removed = queue[index];
|
|
198861
|
+
if (removed?.source === TG_SOURCE) {
|
|
198862
|
+
deps.getBridge()?.queuedLineRemoved(removed.question);
|
|
198863
|
+
}
|
|
198864
|
+
deps.setQueue(removeMainQueueItem(queue, index));
|
|
198865
|
+
deps.paint();
|
|
198866
|
+
},
|
|
198867
|
+
editMainQueue(index) {
|
|
198868
|
+
const edited = editMainQueueItem(deps.getQueue(), index);
|
|
198869
|
+
if (edited === undefined)
|
|
198870
|
+
return;
|
|
198871
|
+
deps.setQueue(edited.rest);
|
|
198872
|
+
pendingEdit = pendingQueueEditFor(edited.removed, index);
|
|
198873
|
+
deps.composer.setText(edited.text);
|
|
198874
|
+
deps.composer.focus();
|
|
198875
|
+
deps.paint();
|
|
198876
|
+
},
|
|
198877
|
+
requeuePendingEdit(line, displayLine) {
|
|
198878
|
+
if (pendingEdit === undefined)
|
|
198879
|
+
return false;
|
|
198880
|
+
const edit = pendingEdit;
|
|
198881
|
+
pendingEdit = undefined;
|
|
198882
|
+
deps.setQueue(reinsertEditedMainQueueItem(deps.getQueue(), edit, line, displayLine));
|
|
198883
|
+
deps.paint();
|
|
198884
|
+
return true;
|
|
198885
|
+
},
|
|
198886
|
+
hasPendingEdit: () => pendingEdit !== undefined,
|
|
198887
|
+
stopRemoteForSessionSwitch() {
|
|
198888
|
+
const bridge = deps.getBridge();
|
|
198889
|
+
if (bridge?.keepingTopic === true) {
|
|
198890
|
+
bridge.sessionLeaving();
|
|
198891
|
+
return;
|
|
198892
|
+
}
|
|
198893
|
+
if (bridge?.active !== true)
|
|
198894
|
+
return;
|
|
198895
|
+
const closing = bridge.disable();
|
|
198896
|
+
deps.say(`\u25C7 remote control is turning off: the session changed, and its Telegram topic is being archived. Turn it on again with /remote-control <name>.
|
|
198897
|
+
`);
|
|
198898
|
+
closing.then(() => deps.say(`\u25C7 remote control off: the topic is archived.
|
|
198899
|
+
`));
|
|
198900
|
+
},
|
|
198901
|
+
dropQueuedTelegramLines() {
|
|
198902
|
+
const { kept, dropped } = dropQueuedBySource(deps.getQueue(), TG_SOURCE);
|
|
198903
|
+
if (dropped.length === 0)
|
|
198904
|
+
return [];
|
|
198905
|
+
deps.setQueue(kept);
|
|
198906
|
+
deps.paint();
|
|
198907
|
+
return dropped.map((item) => item.question);
|
|
198908
|
+
},
|
|
198909
|
+
enqueueTelegramLine(text) {
|
|
198910
|
+
const queue = deps.getQueue();
|
|
198911
|
+
queue.push({ id: deps.nextId(), question: text, displayQuestion: deps.summarize(text), source: TG_SOURCE });
|
|
198912
|
+
deps.paint();
|
|
198913
|
+
deps.say(`\u25C7 a line from Telegram is queued as q${queue.length}.
|
|
198914
|
+
`);
|
|
198915
|
+
},
|
|
198916
|
+
runTelegramLine(text) {
|
|
198917
|
+
deps.runLine(text, "operator", TG_SOURCE);
|
|
198918
|
+
},
|
|
198919
|
+
runQueued(item) {
|
|
198920
|
+
deps.runLine(item.question, "operator", item.source);
|
|
198921
|
+
},
|
|
198922
|
+
turnStarted(source) {
|
|
198923
|
+
deps.getBridge()?.turnStarted(source);
|
|
198924
|
+
},
|
|
198925
|
+
assistantText(text) {
|
|
198926
|
+
deps.getBridge()?.assistantText(text);
|
|
198927
|
+
},
|
|
198928
|
+
wrapOnToolCall(base) {
|
|
198929
|
+
return (...args) => {
|
|
198930
|
+
deps.getBridge()?.toolCall(typeof args[0] === "string" ? args[0] : undefined);
|
|
198931
|
+
base?.(...args);
|
|
198932
|
+
};
|
|
198933
|
+
},
|
|
198934
|
+
turnSettled(outcome) {
|
|
198935
|
+
const bridge = deps.getBridge();
|
|
198936
|
+
return bridge === undefined ? Promise.resolve() : bridge.turnSettled({ failed: outcome.failed || outcome.aborted });
|
|
198937
|
+
}
|
|
198938
|
+
};
|
|
198939
|
+
}
|
|
198940
|
+
var init_remote_queue_wiring = __esm(() => {
|
|
198941
|
+
init_shell_bridge();
|
|
198942
|
+
});
|
|
198943
|
+
|
|
198944
|
+
// src/tui/turn-loop.ts
|
|
198945
|
+
function createTurnLoop(deps) {
|
|
198946
|
+
let queue = [];
|
|
198947
|
+
let seq = 0;
|
|
198948
|
+
const nextId = () => `mq${seq++}`;
|
|
198949
|
+
const enqueue = (line, displayLine, source) => {
|
|
198950
|
+
queue.push({ id: nextId(), question: line, displayQuestion: displayLine, ...source !== undefined ? { source } : {} });
|
|
198951
|
+
deps.paint();
|
|
198952
|
+
return queue.length;
|
|
198953
|
+
};
|
|
198954
|
+
const remote = createRemoteQueueWiring({
|
|
198955
|
+
...deps.remote,
|
|
198956
|
+
getQueue: () => queue,
|
|
198957
|
+
setQueue: (next) => {
|
|
198958
|
+
queue = next;
|
|
198959
|
+
},
|
|
198960
|
+
nextId,
|
|
198961
|
+
paint: deps.paint,
|
|
198962
|
+
say: deps.say,
|
|
198963
|
+
runLine: deps.runLine
|
|
198964
|
+
});
|
|
198965
|
+
const drain = createMainQueueDrain({
|
|
198966
|
+
forced: deps.forced,
|
|
198967
|
+
queue: () => queue,
|
|
198968
|
+
paint: deps.paint,
|
|
198969
|
+
run: (next) => remote.runQueued(next),
|
|
198970
|
+
turnWorkInFlight: deps.turnWorkInFlight,
|
|
198971
|
+
onSettle: deps.onSettle,
|
|
198972
|
+
onDetachedFailure: deps.onDetachedFailure
|
|
198973
|
+
});
|
|
198974
|
+
return {
|
|
198975
|
+
get queue() {
|
|
198976
|
+
return queue;
|
|
198977
|
+
},
|
|
198978
|
+
replaceQueue(next) {
|
|
198979
|
+
queue = next;
|
|
198980
|
+
},
|
|
198981
|
+
enqueue,
|
|
198982
|
+
enqueueWhileBusy(line, displayLine, commandName, source) {
|
|
198983
|
+
if (deferredBusyAction(line, commandName) !== "queue")
|
|
198984
|
+
return false;
|
|
198985
|
+
const position = enqueue(line, displayLine, source);
|
|
198986
|
+
deps.say(`${queuedCommandNotice(position)}
|
|
198987
|
+
`);
|
|
198988
|
+
return true;
|
|
198989
|
+
},
|
|
198990
|
+
drainAfterRecipientChoice(isIdle) {
|
|
198991
|
+
drainIdleMainQueue(queue, {
|
|
198992
|
+
isIdle,
|
|
198993
|
+
takeForced: () => deps.forced.takeNext(),
|
|
198994
|
+
dispatch: (next) => {
|
|
198995
|
+
deps.paint();
|
|
198996
|
+
remote.runQueued(next);
|
|
198997
|
+
}
|
|
198998
|
+
});
|
|
198999
|
+
},
|
|
199000
|
+
remote,
|
|
199001
|
+
drain
|
|
199002
|
+
};
|
|
199003
|
+
}
|
|
199004
|
+
var init_turn_loop = __esm(() => {
|
|
199005
|
+
init_busy_dispatch();
|
|
199006
|
+
init_remote_queue_wiring();
|
|
199007
|
+
});
|
|
199008
|
+
|
|
198087
199009
|
// src/tui/addressee-queue.ts
|
|
198088
199010
|
function emptyAddresseeQueues() {
|
|
198089
199011
|
return new Map;
|
|
@@ -198866,7 +199788,7 @@ function renderExternalTranscript(events, options = {}) {
|
|
|
198866
199788
|
if (options.summary !== false && events.length > 0) {
|
|
198867
199789
|
lines.push("", formatTranscriptFooter(foldExternalTranscript(events)));
|
|
198868
199790
|
}
|
|
198869
|
-
return options.width === undefined ? lines : lines.map((line) =>
|
|
199791
|
+
return options.width === undefined ? lines : lines.map((line) => clip11(line, options.width ?? 0));
|
|
198870
199792
|
}
|
|
198871
199793
|
function formatExternalWork(events, options = {}) {
|
|
198872
199794
|
return renderExternalTranscript(events, options).join(`
|
|
@@ -199031,7 +199953,7 @@ function describeSkips(skippedLines) {
|
|
|
199031
199953
|
function plural5(count, singular, pluralForm) {
|
|
199032
199954
|
return `${count} ${count === 1 ? singular : pluralForm ?? `${singular}s`}`;
|
|
199033
199955
|
}
|
|
199034
|
-
function
|
|
199956
|
+
function clip11(value, width) {
|
|
199035
199957
|
if (width <= 0 || value.length <= width)
|
|
199036
199958
|
return value;
|
|
199037
199959
|
return `${value.slice(0, Math.max(1, width - 1))}\u2026`;
|
|
@@ -199061,7 +199983,7 @@ var init_external_transcript = __esm(() => {
|
|
|
199061
199983
|
});
|
|
199062
199984
|
|
|
199063
199985
|
// src/tui/external-inspector.ts
|
|
199064
|
-
function
|
|
199986
|
+
function paintContent4(otui, renderer, body, id, content) {
|
|
199065
199987
|
if (otui === undefined || otui === null || body === undefined || body === null)
|
|
199066
199988
|
return;
|
|
199067
199989
|
const parent = body;
|
|
@@ -199134,18 +200056,18 @@ function presentExternalInspector(openModalFn, otui, chrome, options) {
|
|
|
199134
200056
|
const renderer = options.renderer ?? chrome?.renderer;
|
|
199135
200057
|
const current = options.store.get(options.id);
|
|
199136
200058
|
if (current === undefined) {
|
|
199137
|
-
|
|
200059
|
+
paintContent4(otui, renderer, body, "external-inspector-body", EXTERNAL_RUN_GONE);
|
|
199138
200060
|
return;
|
|
199139
200061
|
}
|
|
199140
200062
|
if (tabId === "meta") {
|
|
199141
|
-
metaNode =
|
|
200063
|
+
metaNode = paintContent4(otui, renderer, body, "external-inspector-meta", formatExternalMeta(current));
|
|
199142
200064
|
return;
|
|
199143
200065
|
}
|
|
199144
200066
|
if (tabId === "command") {
|
|
199145
|
-
commandNode =
|
|
200067
|
+
commandNode = paintContent4(otui, renderer, body, "external-inspector-command", formatExternalCommand(current));
|
|
199146
200068
|
return;
|
|
199147
200069
|
}
|
|
199148
|
-
workNode =
|
|
200070
|
+
workNode = paintContent4(otui, renderer, body, "external-inspector-work", formatExternalWork(current.events));
|
|
199149
200071
|
},
|
|
199150
200072
|
onClose: () => {
|
|
199151
200073
|
unsubscribe?.();
|
|
@@ -199169,333 +200091,6 @@ var init_external_inspector = __esm(() => {
|
|
|
199169
200091
|
];
|
|
199170
200092
|
});
|
|
199171
200093
|
|
|
199172
|
-
// src/tui/background-job-session.ts
|
|
199173
|
-
function appendBounded(output, chunk) {
|
|
199174
|
-
const combined = output + chunk;
|
|
199175
|
-
return combined.length > MAX_BACKGROUND_JOB_OUTPUT_CHARS ? combined.slice(-MAX_BACKGROUND_JOB_OUTPUT_CHARS) : combined;
|
|
199176
|
-
}
|
|
199177
|
-
function clip11(s, max) {
|
|
199178
|
-
if (max <= 0) {
|
|
199179
|
-
return "";
|
|
199180
|
-
}
|
|
199181
|
-
if (s.length <= max) {
|
|
199182
|
-
return s;
|
|
199183
|
-
}
|
|
199184
|
-
return `${s.slice(0, Math.max(1, max - 1))}\u2026`;
|
|
199185
|
-
}
|
|
199186
|
-
function formatJobListHeader(count) {
|
|
199187
|
-
return `Background Jobs ${count}`;
|
|
199188
|
-
}
|
|
199189
|
-
function formatJobRow(entry, width = SIDEBAR_TEXT_WIDTH) {
|
|
199190
|
-
const glyph = STATUS_GLYPH4[entry.status];
|
|
199191
|
-
const budget = Math.max(1, width - glyph.length - 1);
|
|
199192
|
-
const cmd = clip11(entry.command, budget);
|
|
199193
|
-
return clip11(`${glyph} ${cmd}`, width);
|
|
199194
|
-
}
|
|
199195
|
-
function formatJobDuration(entry, now) {
|
|
199196
|
-
const started = Date.parse(entry.startedAt);
|
|
199197
|
-
const ended = entry.endedAt !== undefined ? Date.parse(entry.endedAt) : now;
|
|
199198
|
-
return formatDuration(ended - started);
|
|
199199
|
-
}
|
|
199200
|
-
function formatJobMeta(entry, now = Date.now()) {
|
|
199201
|
-
const rows = [
|
|
199202
|
-
["Id", entry.jobId],
|
|
199203
|
-
["Pid", String(entry.pid)],
|
|
199204
|
-
["Status", entry.status],
|
|
199205
|
-
["Command", entry.command],
|
|
199206
|
-
["Started", entry.startedAt],
|
|
199207
|
-
["Ended", entry.endedAt ?? "\u2014"],
|
|
199208
|
-
["Duration", formatJobDuration(entry, now)],
|
|
199209
|
-
["Exit code", entry.exitCode !== undefined ? String(entry.exitCode) : "\u2014"]
|
|
199210
|
-
];
|
|
199211
|
-
if (entry.killReason !== undefined) {
|
|
199212
|
-
rows.push(["Kill reason", entry.killReason]);
|
|
199213
|
-
}
|
|
199214
|
-
const width = rows.reduce((max, [label]) => Math.max(max, label.length), 0);
|
|
199215
|
-
return rows.map(([label, value]) => `${label.padEnd(width)} ${value}`).join(`
|
|
199216
|
-
`);
|
|
199217
|
-
}
|
|
199218
|
-
function formatJobOutput(entry) {
|
|
199219
|
-
return entry.output.length > 0 ? entry.output : "(no output yet)";
|
|
199220
|
-
}
|
|
199221
|
-
|
|
199222
|
-
class BackgroundJobStore {
|
|
199223
|
-
jobs = new Map;
|
|
199224
|
-
pending = new Map;
|
|
199225
|
-
listeners = new Set;
|
|
199226
|
-
apply(event) {
|
|
199227
|
-
switch (event.type) {
|
|
199228
|
-
case "start":
|
|
199229
|
-
this.pending.set(event.jobId, {
|
|
199230
|
-
jobId: event.jobId,
|
|
199231
|
-
command: event.command,
|
|
199232
|
-
pid: event.pid,
|
|
199233
|
-
status: "running",
|
|
199234
|
-
startedAt: event.startedAt,
|
|
199235
|
-
output: ""
|
|
199236
|
-
});
|
|
199237
|
-
return;
|
|
199238
|
-
case "phase": {
|
|
199239
|
-
const entry = this.pending.get(event.jobId);
|
|
199240
|
-
if (entry === undefined) {
|
|
199241
|
-
return;
|
|
199242
|
-
}
|
|
199243
|
-
this.pending.delete(event.jobId);
|
|
199244
|
-
this.jobs.set(event.jobId, entry);
|
|
199245
|
-
this.emit({ id: event.jobId, kind: "start" });
|
|
199246
|
-
return;
|
|
199247
|
-
}
|
|
199248
|
-
case "output": {
|
|
199249
|
-
const hidden = this.pending.get(event.jobId);
|
|
199250
|
-
if (hidden !== undefined) {
|
|
199251
|
-
hidden.output = appendBounded(hidden.output, event.chunk);
|
|
199252
|
-
return;
|
|
199253
|
-
}
|
|
199254
|
-
const current = this.jobs.get(event.jobId);
|
|
199255
|
-
if (current === undefined) {
|
|
199256
|
-
return;
|
|
199257
|
-
}
|
|
199258
|
-
current.output = appendBounded(current.output, event.chunk);
|
|
199259
|
-
this.emit({ id: event.jobId, kind: "output" });
|
|
199260
|
-
return;
|
|
199261
|
-
}
|
|
199262
|
-
case "exit": {
|
|
199263
|
-
if (this.pending.delete(event.jobId)) {
|
|
199264
|
-
return;
|
|
199265
|
-
}
|
|
199266
|
-
const current = this.jobs.get(event.jobId);
|
|
199267
|
-
if (current === undefined) {
|
|
199268
|
-
return;
|
|
199269
|
-
}
|
|
199270
|
-
current.status = event.status;
|
|
199271
|
-
current.endedAt = event.endedAt;
|
|
199272
|
-
if (event.exitCode !== undefined) {
|
|
199273
|
-
current.exitCode = event.exitCode;
|
|
199274
|
-
}
|
|
199275
|
-
if (event.killReason !== undefined) {
|
|
199276
|
-
current.killReason = event.killReason;
|
|
199277
|
-
}
|
|
199278
|
-
this.emit({ id: event.jobId, kind: "exit" });
|
|
199279
|
-
return;
|
|
199280
|
-
}
|
|
199281
|
-
}
|
|
199282
|
-
}
|
|
199283
|
-
get(jobId) {
|
|
199284
|
-
return this.jobs.get(jobId);
|
|
199285
|
-
}
|
|
199286
|
-
list() {
|
|
199287
|
-
return [...this.jobs.values()];
|
|
199288
|
-
}
|
|
199289
|
-
subscribe(listener) {
|
|
199290
|
-
this.listeners.add(listener);
|
|
199291
|
-
return () => {
|
|
199292
|
-
this.listeners.delete(listener);
|
|
199293
|
-
};
|
|
199294
|
-
}
|
|
199295
|
-
removeAll() {
|
|
199296
|
-
this.pending.clear();
|
|
199297
|
-
if (this.jobs.size === 0) {
|
|
199298
|
-
return;
|
|
199299
|
-
}
|
|
199300
|
-
const ids = [...this.jobs.keys()];
|
|
199301
|
-
this.jobs.clear();
|
|
199302
|
-
for (const id of ids) {
|
|
199303
|
-
this.emit({ id, kind: "exit" });
|
|
199304
|
-
}
|
|
199305
|
-
}
|
|
199306
|
-
emit(hint) {
|
|
199307
|
-
for (const listener of this.listeners) {
|
|
199308
|
-
try {
|
|
199309
|
-
listener(hint);
|
|
199310
|
-
} catch {}
|
|
199311
|
-
}
|
|
199312
|
-
}
|
|
199313
|
-
}
|
|
199314
|
-
var MAX_BACKGROUND_JOB_OUTPUT_CHARS = 20000, STATUS_GLYPH4;
|
|
199315
|
-
var init_background_job_session = __esm(() => {
|
|
199316
|
-
init_shell_chrome();
|
|
199317
|
-
init_duration_format();
|
|
199318
|
-
STATUS_GLYPH4 = {
|
|
199319
|
-
running: "\u25D0",
|
|
199320
|
-
completed: "\u25CF",
|
|
199321
|
-
failed: "\u25B2",
|
|
199322
|
-
killed: "\u2717"
|
|
199323
|
-
};
|
|
199324
|
-
});
|
|
199325
|
-
|
|
199326
|
-
// src/tui/background-job-inspector.ts
|
|
199327
|
-
function resolveTarget(otui, renderer, body) {
|
|
199328
|
-
if (otui === undefined || otui === null || body === undefined || body === null) {
|
|
199329
|
-
return;
|
|
199330
|
-
}
|
|
199331
|
-
const parent = body;
|
|
199332
|
-
if (parent.add === undefined) {
|
|
199333
|
-
return;
|
|
199334
|
-
}
|
|
199335
|
-
const scrollCtor = otui.ScrollBoxRenderable;
|
|
199336
|
-
if (scrollCtor === undefined) {
|
|
199337
|
-
return { add: parent.add.bind(parent) };
|
|
199338
|
-
}
|
|
199339
|
-
const scroll = new scrollCtor(renderer, {
|
|
199340
|
-
id: "job-inspector-scroll",
|
|
199341
|
-
flexGrow: 1,
|
|
199342
|
-
minHeight: 0,
|
|
199343
|
-
scrollY: true,
|
|
199344
|
-
stickyScroll: true,
|
|
199345
|
-
stickyStart: "bottom",
|
|
199346
|
-
contentOptions: { flexDirection: "column" }
|
|
199347
|
-
});
|
|
199348
|
-
parent.add(scroll);
|
|
199349
|
-
const target = scroll.content ?? scroll;
|
|
199350
|
-
if (target.add === undefined) {
|
|
199351
|
-
return;
|
|
199352
|
-
}
|
|
199353
|
-
return { add: target.add.bind(target) };
|
|
199354
|
-
}
|
|
199355
|
-
function paintContent4(otui, renderer, body, content) {
|
|
199356
|
-
const target = resolveTarget(otui, renderer, body);
|
|
199357
|
-
const textCtor = otui.TextRenderable;
|
|
199358
|
-
if (target === undefined || textCtor === undefined) {
|
|
199359
|
-
return;
|
|
199360
|
-
}
|
|
199361
|
-
const node = new textCtor(renderer, { id: "job-inspector-output", content });
|
|
199362
|
-
target.add(node);
|
|
199363
|
-
return node;
|
|
199364
|
-
}
|
|
199365
|
-
function paintMetaTab(otui, renderer, body, entry, registry, jobId) {
|
|
199366
|
-
const target = resolveTarget(otui, renderer, body);
|
|
199367
|
-
const textCtor = otui.TextRenderable;
|
|
199368
|
-
if (target === undefined || textCtor === undefined) {
|
|
199369
|
-
return;
|
|
199370
|
-
}
|
|
199371
|
-
const metaNode = new textCtor(renderer, { id: "job-inspector-meta", content: formatJobMeta(entry) });
|
|
199372
|
-
target.add(metaNode);
|
|
199373
|
-
const killStatusNode = new textCtor(renderer, { id: "job-inspector-kill-status", content: "" });
|
|
199374
|
-
const killNode = new textCtor(renderer, {
|
|
199375
|
-
id: "job-inspector-kill",
|
|
199376
|
-
content: "[ Kill ]",
|
|
199377
|
-
onMouseDown: () => {
|
|
199378
|
-
registry.kill(jobId).then((result) => {
|
|
199379
|
-
if (!result.ok) {
|
|
199380
|
-
killStatusNode.content = `Kill failed: ${result.error}`;
|
|
199381
|
-
}
|
|
199382
|
-
});
|
|
199383
|
-
}
|
|
199384
|
-
});
|
|
199385
|
-
target.add(killNode);
|
|
199386
|
-
target.add(killStatusNode);
|
|
199387
|
-
return metaNode;
|
|
199388
|
-
}
|
|
199389
|
-
function inspectorTitleForJob(entry) {
|
|
199390
|
-
return entry.command;
|
|
199391
|
-
}
|
|
199392
|
-
function presentJobInspector(openModalFn, otui, chrome, options) {
|
|
199393
|
-
const entry = options.store.get(options.id);
|
|
199394
|
-
if (entry === undefined) {
|
|
199395
|
-
return;
|
|
199396
|
-
}
|
|
199397
|
-
let outputNode;
|
|
199398
|
-
let metaNode;
|
|
199399
|
-
let unsubscribe;
|
|
199400
|
-
const refresh = (hint) => {
|
|
199401
|
-
if (hint !== undefined && hint.id !== options.id) {
|
|
199402
|
-
return;
|
|
199403
|
-
}
|
|
199404
|
-
const current = options.store.get(options.id);
|
|
199405
|
-
if (current === undefined) {
|
|
199406
|
-
if (outputNode !== undefined) {
|
|
199407
|
-
outputNode.content = GONE_TEXT;
|
|
199408
|
-
}
|
|
199409
|
-
if (metaNode !== undefined) {
|
|
199410
|
-
metaNode.content = GONE_TEXT;
|
|
199411
|
-
}
|
|
199412
|
-
return;
|
|
199413
|
-
}
|
|
199414
|
-
if (outputNode !== undefined) {
|
|
199415
|
-
outputNode.content = formatJobOutput(current);
|
|
199416
|
-
}
|
|
199417
|
-
if (metaNode !== undefined) {
|
|
199418
|
-
metaNode.content = formatJobMeta(current);
|
|
199419
|
-
}
|
|
199420
|
-
};
|
|
199421
|
-
const handle = openModalFn(otui, chrome, {
|
|
199422
|
-
title: inspectorTitleForJob(entry),
|
|
199423
|
-
tabs: [
|
|
199424
|
-
{ id: "output", label: "Output" },
|
|
199425
|
-
{ id: "meta", label: "Meta" }
|
|
199426
|
-
],
|
|
199427
|
-
initialTab: "output",
|
|
199428
|
-
footer: JOB_INSPECTOR_FOOTER,
|
|
199429
|
-
renderTab: (tabId, body) => {
|
|
199430
|
-
const renderer = options.renderer ?? chrome?.renderer;
|
|
199431
|
-
const current = options.store.get(options.id);
|
|
199432
|
-
if (current === undefined) {
|
|
199433
|
-
paintContent4(otui, renderer, body, GONE_TEXT);
|
|
199434
|
-
return;
|
|
199435
|
-
}
|
|
199436
|
-
if (tabId === "meta") {
|
|
199437
|
-
metaNode = paintMetaTab(otui, renderer, body, current, options.registry, options.id);
|
|
199438
|
-
outputNode = undefined;
|
|
199439
|
-
return;
|
|
199440
|
-
}
|
|
199441
|
-
outputNode = paintContent4(otui, renderer, body, formatJobOutput(current));
|
|
199442
|
-
metaNode = undefined;
|
|
199443
|
-
},
|
|
199444
|
-
onClose: () => {
|
|
199445
|
-
unsubscribe?.();
|
|
199446
|
-
}
|
|
199447
|
-
});
|
|
199448
|
-
if (handle === undefined) {
|
|
199449
|
-
return;
|
|
199450
|
-
}
|
|
199451
|
-
unsubscribe = options.store.subscribe(refresh);
|
|
199452
|
-
return handle;
|
|
199453
|
-
}
|
|
199454
|
-
function openJobInspector(otui, chrome, options) {
|
|
199455
|
-
return presentJobInspector((hostOtui, hostChrome, input) => openModal(hostOtui, hostChrome, input), otui, chrome, options);
|
|
199456
|
-
}
|
|
199457
|
-
function paintBackgroundJobSidebar(otui, renderer, parent, entries, options) {
|
|
199458
|
-
const box = parent;
|
|
199459
|
-
const ctor = otui.TextRenderable;
|
|
199460
|
-
if (box.add === undefined || ctor === undefined) {
|
|
199461
|
-
return;
|
|
199462
|
-
}
|
|
199463
|
-
if (box.getChildren !== undefined && box.remove !== undefined) {
|
|
199464
|
-
for (const child of [...box.getChildren()]) {
|
|
199465
|
-
box.remove(child);
|
|
199466
|
-
}
|
|
199467
|
-
}
|
|
199468
|
-
if (entries.length === 0) {
|
|
199469
|
-
return;
|
|
199470
|
-
}
|
|
199471
|
-
const width = options.width ?? SIDEBAR_TEXT_WIDTH;
|
|
199472
|
-
box.add(new ctor(renderer, {
|
|
199473
|
-
id: "sb-jobs-h",
|
|
199474
|
-
content: formatJobListHeader(entries.length)
|
|
199475
|
-
}));
|
|
199476
|
-
for (const entry of entries) {
|
|
199477
|
-
const id = entry.jobId;
|
|
199478
|
-
box.add(new ctor(renderer, {
|
|
199479
|
-
id: `sb-job-${id}`,
|
|
199480
|
-
content: formatJobRow(entry, width),
|
|
199481
|
-
onMouseDown: () => {
|
|
199482
|
-
options.onOpen(id);
|
|
199483
|
-
}
|
|
199484
|
-
}));
|
|
199485
|
-
}
|
|
199486
|
-
}
|
|
199487
|
-
var JOB_INSPECTOR_FOOTER, GONE_TEXT = "Background job is gone.";
|
|
199488
|
-
var init_background_job_inspector = __esm(() => {
|
|
199489
|
-
init_modal_host();
|
|
199490
|
-
init_shell_chrome();
|
|
199491
|
-
init_background_job_session();
|
|
199492
|
-
JOB_INSPECTOR_FOOTER = [
|
|
199493
|
-
{ key: "\u2190/\u2192", label: "tabs" },
|
|
199494
|
-
{ key: "k", label: "kill" },
|
|
199495
|
-
{ key: "esc", label: "close" }
|
|
199496
|
-
];
|
|
199497
|
-
});
|
|
199498
|
-
|
|
199499
200094
|
// src/tui/execution-plan-panel.ts
|
|
199500
200095
|
function truncate7(text, width) {
|
|
199501
200096
|
if (width <= 0)
|
|
@@ -203455,8 +204050,12 @@ async function launchTuiAgentShell(opts) {
|
|
|
203455
204050
|
sections[section] = text;
|
|
203456
204051
|
}
|
|
203457
204052
|
const hold = leaseView()?.banner();
|
|
204053
|
+
const work = workSidebarSnapshot(sessions.list(), jobs.list());
|
|
204054
|
+
delete sections.subagents;
|
|
204055
|
+
delete sections.jobs;
|
|
203458
204056
|
return {
|
|
203459
|
-
sections,
|
|
204057
|
+
sections: { ...sections, ...work.sections },
|
|
204058
|
+
items: work.items,
|
|
203460
204059
|
approvalsPending: liveApprovals?.pending() ?? 0,
|
|
203461
204060
|
...hold !== undefined ? { hold } : {},
|
|
203462
204061
|
...updateAvailableVersion !== undefined ? { updateAvailable: updateAvailableVersion } : {},
|
|
@@ -203469,7 +204068,23 @@ async function launchTuiAgentShell(opts) {
|
|
|
203469
204068
|
geometry: sidebarGeometry,
|
|
203470
204069
|
snapshot: () => cardSnapshot,
|
|
203471
204070
|
onOpenCard: (cardId) => {
|
|
203472
|
-
openSidebarCard(otui, chrome, cardId, {
|
|
204071
|
+
openSidebarCard(otui, chrome, cardId, {
|
|
204072
|
+
snapshot: sidebarSnapshot,
|
|
204073
|
+
runCommand: (command) => runLine(command),
|
|
204074
|
+
subscribe: (refresh) => {
|
|
204075
|
+
const offAgents = sessions.subscribe(refresh);
|
|
204076
|
+
const offJobs = jobs.subscribe(refresh);
|
|
204077
|
+
return () => {
|
|
204078
|
+
offAgents();
|
|
204079
|
+
offJobs();
|
|
204080
|
+
};
|
|
204081
|
+
},
|
|
204082
|
+
renderItem: (section, item, body) => renderWorkInspector(otui, chrome, body, section, item.id, {
|
|
204083
|
+
agents: sessions,
|
|
204084
|
+
jobs,
|
|
204085
|
+
registry: { kill: (id) => deps.jobRegistry !== undefined ? deps.jobRegistry.kill(id) : Promise.resolve({ ok: false, error: "Job registry is unavailable." }) }
|
|
204086
|
+
})
|
|
204087
|
+
});
|
|
203473
204088
|
}
|
|
203474
204089
|
});
|
|
203475
204090
|
const tickSidebarCards = () => {
|
|
@@ -204668,7 +205283,14 @@ async function launchTuiAgentShell(opts) {
|
|
|
204668
205283
|
foregroundOperation.invalidateSession();
|
|
204669
205284
|
io.resetStream();
|
|
204670
205285
|
blockIo.resetReasoningLiveState();
|
|
204671
|
-
forceHandoff.
|
|
205286
|
+
const parked = forceHandoff.drainPending();
|
|
205287
|
+
if (parked.length > 0) {
|
|
205288
|
+
turnLoop.replaceQueue([...parked, ...turnLoop.queue]);
|
|
205289
|
+
paintMainQueue();
|
|
205290
|
+
io.onSystem?.(parked.length === 1 ? `\u25C7 the forced line is back in the main queue.
|
|
205291
|
+
` : `\u25C7 ${parked.length} forced lines are back in the main queue.
|
|
205292
|
+
`);
|
|
205293
|
+
}
|
|
204672
205294
|
suggestionGate.cancel();
|
|
204673
205295
|
if (sessionPersistTimer !== undefined) {
|
|
204674
205296
|
clearTimeout(sessionPersistTimer);
|
|
@@ -206102,8 +206724,18 @@ ${formatThemeList(getThemeId())}`);
|
|
|
206102
206724
|
turnLoop.replaceQueue(removeMainQueueItem(turnLoop.queue, index));
|
|
206103
206725
|
paintMainQueue();
|
|
206104
206726
|
forceForegroundQueueItem(foregroundOperation, forceHandoff, item, {
|
|
206105
|
-
announce: () => io.onSystem?.(`\u25C7 main turn interrupted \u2014 q${index + 1} will run next.
|
|
206727
|
+
announce: (interrupted) => io.onSystem?.(interrupted ? `\u25C7 main turn interrupted \u2014 q${index + 1} will run next.
|
|
206728
|
+
` : `\u25C7 q${index + 1} will run next.
|
|
206106
206729
|
`),
|
|
206730
|
+
isBusy: () => chrome.isBusy(),
|
|
206731
|
+
resume: () => drainSites.onLeaseRelease(),
|
|
206732
|
+
requeue: (items) => {
|
|
206733
|
+
if (foregroundOperation.isDisposed)
|
|
206734
|
+
return;
|
|
206735
|
+
turnLoop.replaceQueue([...items, ...turnLoop.queue]);
|
|
206736
|
+
paintMainQueue();
|
|
206737
|
+
drainSites.onLeaseRelease();
|
|
206738
|
+
},
|
|
206107
206739
|
run: (next) => remoteQueue.runQueued(next)
|
|
206108
206740
|
});
|
|
206109
206741
|
};
|
|
@@ -208052,6 +208684,7 @@ var init_tui_shell = __esm(() => {
|
|
|
208052
208684
|
init_shell_chrome();
|
|
208053
208685
|
init_sidebar_geometry();
|
|
208054
208686
|
init_sidebar_cards();
|
|
208687
|
+
init_sidebar_work();
|
|
208055
208688
|
init_main_queue_view();
|
|
208056
208689
|
init_turn_loop();
|
|
208057
208690
|
init_queue_nav();
|
|
@@ -235628,7 +236261,7 @@ function buildAcpSessionTools(input) {
|
|
|
235628
236261
|
];
|
|
235629
236262
|
}
|
|
235630
236263
|
var ACP_CLIENT_MCP_TOOL_NAMES;
|
|
235631
|
-
var
|
|
236264
|
+
var init_roster2 = __esm(() => {
|
|
235632
236265
|
init_apply_patch_tool();
|
|
235633
236266
|
init_interactive_tools();
|
|
235634
236267
|
init_shell_exec_tool();
|
|
@@ -236855,7 +237488,7 @@ var init_server2 = __esm(() => {
|
|
|
236855
237488
|
init_session();
|
|
236856
237489
|
init_agent_io();
|
|
236857
237490
|
init_commands2();
|
|
236858
|
-
|
|
237491
|
+
init_roster2();
|
|
236859
237492
|
init_client_requests();
|
|
236860
237493
|
init_dispatch();
|
|
236861
237494
|
init_framing();
|
|
@@ -248409,7 +249042,7 @@ var init_agents_catalog = __esm(() => {
|
|
|
248409
249042
|
});
|
|
248410
249043
|
|
|
248411
249044
|
// src/harness/monitor/reduce.ts
|
|
248412
|
-
function
|
|
249045
|
+
function str6(value) {
|
|
248413
249046
|
return typeof value === "string" && value.length > 0 ? value : undefined;
|
|
248414
249047
|
}
|
|
248415
249048
|
function num3(value) {
|
|
@@ -248418,8 +249051,8 @@ function num3(value) {
|
|
|
248418
249051
|
function modelFrom(data) {
|
|
248419
249052
|
if (data === undefined)
|
|
248420
249053
|
return;
|
|
248421
|
-
const model =
|
|
248422
|
-
const provider =
|
|
249054
|
+
const model = str6(data.model);
|
|
249055
|
+
const provider = str6(data.provider);
|
|
248423
249056
|
if (provider !== undefined && model !== undefined)
|
|
248424
249057
|
return `${provider}/${model}`;
|
|
248425
249058
|
return model ?? provider;
|
|
@@ -248467,7 +249100,7 @@ function reduceAgents(events) {
|
|
|
248467
249100
|
const model = modelFrom(event.data);
|
|
248468
249101
|
if (model !== undefined)
|
|
248469
249102
|
rec.model = model;
|
|
248470
|
-
const source =
|
|
249103
|
+
const source = str6(event.data?.source);
|
|
248471
249104
|
if (source !== undefined)
|
|
248472
249105
|
rec.source = source;
|
|
248473
249106
|
const budgetRemaining = num3(event.data?.budgetRemaining ?? undefined);
|