@pushpalsdev/cli 1.0.78 → 1.0.80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pushpals-cli.js +66 -3
- package/package.json +1 -1
- package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +5 -3
- package/runtime/prompts/workerpals/openai_codex_default_system_prompt.md +1 -0
- package/runtime/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
- package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +1 -0
- package/runtime/sandbox/.pushpals-remotebuddy-fallback.js +560 -22
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +282 -33
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +113 -2
- package/runtime/sandbox/packages/shared/src/client_preflight.ts +2 -0
- package/runtime/sandbox/packages/shared/src/config.ts +1 -6
- package/runtime/sandbox/packages/shared/src/index.ts +19 -0
- package/runtime/sandbox/packages/shared/src/tooling.ts +422 -0
- package/runtime/sandbox/packages/shared/src/vision.ts +12 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_default_system_prompt.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
- package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +1 -0
- package/runtime/vision.example.md +125 -122
|
@@ -1088,7 +1088,7 @@ function loadPushPalsConfig(options = {}) {
|
|
|
1088
1088
|
const canonical = coerceAutonomyComponentConfigKey(rawKey);
|
|
1089
1089
|
if (!canonical)
|
|
1090
1090
|
continue;
|
|
1091
|
-
const parsed =
|
|
1091
|
+
const parsed = rawValue;
|
|
1092
1092
|
remoteAutonomyDispatchByComponent[canonical] = Number.isFinite(parsed) ? Math.max(0, Math.floor(parsed)) : 0;
|
|
1093
1093
|
}
|
|
1094
1094
|
const workerNode = getObject(merged, "workerpals");
|
|
@@ -1603,6 +1603,9 @@ function classifyHeadingBucket(heading) {
|
|
|
1603
1603
|
if (text.includes("non-goal") || text.includes("out of scope") || text.includes("not ")) {
|
|
1604
1604
|
return "nonGoals";
|
|
1605
1605
|
}
|
|
1606
|
+
if (text.includes("testing criteria") || text.includes("test criteria") || text.includes("required tests") || text.includes("required validation") || text.includes("validation criteria")) {
|
|
1607
|
+
return "testingCriteria";
|
|
1608
|
+
}
|
|
1606
1609
|
if (text.includes("measure") || text.includes("metric") || text.includes("good looks like")) {
|
|
1607
1610
|
return "metrics";
|
|
1608
1611
|
}
|
|
@@ -1694,6 +1697,7 @@ function extractVisionKeyItems(markdown) {
|
|
|
1694
1697
|
constraints: [],
|
|
1695
1698
|
nonGoals: [],
|
|
1696
1699
|
metrics: [],
|
|
1700
|
+
testingCriteria: [],
|
|
1697
1701
|
riskPolicy: [],
|
|
1698
1702
|
operatingModel: [],
|
|
1699
1703
|
governance: []
|
|
@@ -1720,11 +1724,24 @@ function extractVisionKeyItems(markdown) {
|
|
|
1720
1724
|
constraints: dedupeAndClamp(buckets.constraints),
|
|
1721
1725
|
nonGoals: dedupeAndClamp(buckets.nonGoals),
|
|
1722
1726
|
metrics: dedupeAndClamp(buckets.metrics),
|
|
1727
|
+
testingCriteria: dedupeAndClamp(buckets.testingCriteria),
|
|
1723
1728
|
riskPolicy: dedupeAndClamp(buckets.riskPolicy),
|
|
1724
1729
|
operatingModel: dedupeAndClamp(buckets.operatingModel),
|
|
1725
1730
|
governance: dedupeAndClamp(buckets.governance)
|
|
1726
1731
|
};
|
|
1727
1732
|
}
|
|
1733
|
+
// packages/shared/src/tooling.ts
|
|
1734
|
+
var KNOWN_TOOL_NAMES = new Set([
|
|
1735
|
+
"bun",
|
|
1736
|
+
"codex",
|
|
1737
|
+
"docker",
|
|
1738
|
+
"gh",
|
|
1739
|
+
"git",
|
|
1740
|
+
"node",
|
|
1741
|
+
"npm",
|
|
1742
|
+
"python",
|
|
1743
|
+
"shell"
|
|
1744
|
+
]);
|
|
1728
1745
|
// packages/shared/src/session_event_visibility.ts
|
|
1729
1746
|
var ALWAYS_VISIBLE_EVENT_TYPES = new Set(["question_asked"]);
|
|
1730
1747
|
// packages/shared/src/localbuddy_runtime.ts
|
|
@@ -2053,6 +2070,10 @@ async function runProcessWithNode(command, opts) {
|
|
|
2053
2070
|
});
|
|
2054
2071
|
}
|
|
2055
2072
|
var cachedCodexCommandPrefix = new Map;
|
|
2073
|
+
function bunCodexCommandFromEnv(env) {
|
|
2074
|
+
const bunBin = (env.PUSHPALS_BUN_BIN ?? "").trim();
|
|
2075
|
+
return bunBin ? [bunBin, "x", "--yes", "@openai/codex"] : [];
|
|
2076
|
+
}
|
|
2056
2077
|
async function resolveCodexCommandPrefix(configuredCommand) {
|
|
2057
2078
|
const override = codexCommandOverrideParts(configuredCommand);
|
|
2058
2079
|
const cacheKey = override.join("\x00");
|
|
@@ -2071,6 +2092,7 @@ async function resolveCodexCommandPrefix(configuredCommand) {
|
|
|
2071
2092
|
candidates.push(cmd);
|
|
2072
2093
|
};
|
|
2073
2094
|
pushCandidate(preferred);
|
|
2095
|
+
pushCandidate(bunCodexCommandFromEnv(process.env));
|
|
2074
2096
|
const execPath = (process.execPath ?? "").trim();
|
|
2075
2097
|
if (execPath) {
|
|
2076
2098
|
const lower = execPath.toLowerCase();
|
|
@@ -4054,7 +4076,7 @@ class PersistentSessionMemory {
|
|
|
4054
4076
|
}
|
|
4055
4077
|
|
|
4056
4078
|
// apps/remotebuddy/src/remotebuddy_main.ts
|
|
4057
|
-
import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
|
|
4079
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
|
|
4058
4080
|
import { resolve as resolve5 } from "path";
|
|
4059
4081
|
|
|
4060
4082
|
// apps/remotebuddy/src/autonomous_engine.ts
|
|
@@ -4491,6 +4513,12 @@ function asNumber(value, fallback = 0) {
|
|
|
4491
4513
|
const n = Number(value);
|
|
4492
4514
|
return Number.isFinite(n) ? n : fallback;
|
|
4493
4515
|
}
|
|
4516
|
+
function compactStatusDetail(value, max = 240) {
|
|
4517
|
+
const normalized = value.replace(/\s+/g, " ").trim();
|
|
4518
|
+
if (normalized.length <= max)
|
|
4519
|
+
return normalized;
|
|
4520
|
+
return `${normalized.slice(0, Math.max(0, max - 3))}...`;
|
|
4521
|
+
}
|
|
4494
4522
|
function uniqueLowercaseTokens(values, max = 24) {
|
|
4495
4523
|
const out = [];
|
|
4496
4524
|
const seen = new Set;
|
|
@@ -4505,6 +4533,144 @@ function uniqueLowercaseTokens(values, max = 24) {
|
|
|
4505
4533
|
}
|
|
4506
4534
|
return out;
|
|
4507
4535
|
}
|
|
4536
|
+
var CATEGORY_KEYWORD_RULES = [
|
|
4537
|
+
{
|
|
4538
|
+
category: "product_core",
|
|
4539
|
+
pattern: /\b(core|primary|match|game|gameplay|battle|battlefield|player|decision|territor|combat|strategy|mission|workflow|editor|dashboard|api)\b/i
|
|
4540
|
+
},
|
|
4541
|
+
{
|
|
4542
|
+
category: "user_experience",
|
|
4543
|
+
pattern: /\b(user experience|ux|ui|readab|legib|clarity|clear|shell|screen|navigation|control|input|touch|mobile|visual|presentation|feedback|discoverable|usable)\b/i
|
|
4544
|
+
},
|
|
4545
|
+
{
|
|
4546
|
+
category: "onboarding",
|
|
4547
|
+
pattern: /\b(onboard|new user|first[- ]?time|tutorial|learn|help|guide|activation|setup)\b/i
|
|
4548
|
+
},
|
|
4549
|
+
{
|
|
4550
|
+
category: "reliability",
|
|
4551
|
+
pattern: /\b(reliab|stable|stability|startup|trust|regression|failure|resilien|recover|fallback|safe|crash|broken|blocker)\b/i
|
|
4552
|
+
},
|
|
4553
|
+
{
|
|
4554
|
+
category: "validation",
|
|
4555
|
+
pattern: /\b(validation|validate|test|smoke|coverage|browser|e2e|end[- ]?to[- ]?end|ci|check|quality)\b/i
|
|
4556
|
+
},
|
|
4557
|
+
{
|
|
4558
|
+
category: "performance",
|
|
4559
|
+
pattern: /\b(performance|latency|smooth|jitter|lag|throughput|fps|render|memory|speed|fast|responsive)\b/i
|
|
4560
|
+
},
|
|
4561
|
+
{
|
|
4562
|
+
category: "maintainability",
|
|
4563
|
+
pattern: /\b(maintain|refactor|cleanup|architecture|structure|modular|debt|simplify|consistency|coherent)\b/i
|
|
4564
|
+
},
|
|
4565
|
+
{
|
|
4566
|
+
category: "delivery_loop",
|
|
4567
|
+
pattern: /\b(autonom|agent|worker|delivery loop|reliable autonomous delivery|merge|review|pr|pull request|dispatch|orchestrat|planner|compiler|ideation)\b/i
|
|
4568
|
+
},
|
|
4569
|
+
{
|
|
4570
|
+
category: "governance",
|
|
4571
|
+
pattern: /\b(policy|permission|scope|guardrail|risk|constraint|governance|approval|audit|security|non[- ]?goal)\b/i
|
|
4572
|
+
},
|
|
4573
|
+
{
|
|
4574
|
+
category: "growth",
|
|
4575
|
+
pattern: /\b(growth|retention|conversion|activation|adoption|audience|returning|replay|engagement)\b/i
|
|
4576
|
+
},
|
|
4577
|
+
{
|
|
4578
|
+
category: "content",
|
|
4579
|
+
pattern: /\b(content|variety|skin|cosmetic|faction|map|ship|projectile|enemy|level|mode|character)\b/i
|
|
4580
|
+
}
|
|
4581
|
+
];
|
|
4582
|
+
var META_OBJECTIVE_CATEGORIES = new Set([
|
|
4583
|
+
"delivery_loop",
|
|
4584
|
+
"governance",
|
|
4585
|
+
"maintainability"
|
|
4586
|
+
]);
|
|
4587
|
+
function slugifyObjectiveId(value, fallback) {
|
|
4588
|
+
const slug = asString2(value).toLowerCase().replace(/`([^`]+)`/g, "$1").replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "").slice(0, 80);
|
|
4589
|
+
return slug || fallback;
|
|
4590
|
+
}
|
|
4591
|
+
function categorizeVisionText(text) {
|
|
4592
|
+
const matched = [];
|
|
4593
|
+
for (const rule of CATEGORY_KEYWORD_RULES) {
|
|
4594
|
+
if (rule.pattern.test(text))
|
|
4595
|
+
matched.push(rule.category);
|
|
4596
|
+
}
|
|
4597
|
+
if (matched.length === 0) {
|
|
4598
|
+
return { primary: "unknown", secondary: [] };
|
|
4599
|
+
}
|
|
4600
|
+
const [primary, ...secondary] = matched;
|
|
4601
|
+
return {
|
|
4602
|
+
primary,
|
|
4603
|
+
secondary: [...new Set(secondary)].slice(0, 4)
|
|
4604
|
+
};
|
|
4605
|
+
}
|
|
4606
|
+
function sourceBucketSectionRef(sourceBucket, sectionNumbers) {
|
|
4607
|
+
const preferredByBucket = {
|
|
4608
|
+
priorities: ["6", "5", "4"],
|
|
4609
|
+
objectives: ["7", "6", "5"],
|
|
4610
|
+
metrics: ["4", "6"],
|
|
4611
|
+
testing_criteria: ["12", "9", "4"],
|
|
4612
|
+
guardrails: ["9", "10", "3"],
|
|
4613
|
+
constraints: ["9", "5"],
|
|
4614
|
+
risk_policy: ["9", "10"],
|
|
4615
|
+
operating_model: ["11", "7"],
|
|
4616
|
+
governance: ["10", "9"],
|
|
4617
|
+
target_users: ["1"],
|
|
4618
|
+
non_goals: ["5", "9"],
|
|
4619
|
+
section: ["6", "7", "4", "3"]
|
|
4620
|
+
};
|
|
4621
|
+
const available = new Set(sectionNumbers);
|
|
4622
|
+
for (const ref of preferredByBucket[sourceBucket] ?? []) {
|
|
4623
|
+
if (available.has(ref))
|
|
4624
|
+
return ref;
|
|
4625
|
+
}
|
|
4626
|
+
return sectionNumbers[0] ?? "";
|
|
4627
|
+
}
|
|
4628
|
+
function categoryObjectiveType(category) {
|
|
4629
|
+
switch (category) {
|
|
4630
|
+
case "product_core":
|
|
4631
|
+
case "user_experience":
|
|
4632
|
+
case "onboarding":
|
|
4633
|
+
case "content":
|
|
4634
|
+
case "growth":
|
|
4635
|
+
return "feature_small";
|
|
4636
|
+
case "performance":
|
|
4637
|
+
case "reliability":
|
|
4638
|
+
case "maintainability":
|
|
4639
|
+
case "delivery_loop":
|
|
4640
|
+
case "governance":
|
|
4641
|
+
return "small_refactor";
|
|
4642
|
+
case "validation":
|
|
4643
|
+
return "flaky_test";
|
|
4644
|
+
default:
|
|
4645
|
+
return "small_refactor";
|
|
4646
|
+
}
|
|
4647
|
+
}
|
|
4648
|
+
function categoryTriggerType(category, topSignals) {
|
|
4649
|
+
const allowed = [
|
|
4650
|
+
"test_failure",
|
|
4651
|
+
"lint_failure",
|
|
4652
|
+
"typecheck_failure",
|
|
4653
|
+
"queue_health",
|
|
4654
|
+
"regret_signal"
|
|
4655
|
+
];
|
|
4656
|
+
const strongestSignal = topSignals.map((signal) => ({
|
|
4657
|
+
type: asString2(signal.type),
|
|
4658
|
+
value: clamp012(asNumber(signal.value, 0))
|
|
4659
|
+
})).filter((signal) => allowed.includes(signal.type)).sort((a, b) => b.value - a.value)[0];
|
|
4660
|
+
if (category === "validation") {
|
|
4661
|
+
return strongestSignal?.type === "test_failure" ? "test_failure" : "queue_health";
|
|
4662
|
+
}
|
|
4663
|
+
if (category === "performance" || category === "reliability") {
|
|
4664
|
+
return strongestSignal?.type ?? "queue_health";
|
|
4665
|
+
}
|
|
4666
|
+
if (category === "delivery_loop" || category === "governance" || category === "maintainability") {
|
|
4667
|
+
return strongestSignal?.type === "regret_signal" ? "regret_signal" : "queue_health";
|
|
4668
|
+
}
|
|
4669
|
+
return "queue_health";
|
|
4670
|
+
}
|
|
4671
|
+
function isMetaRepoObjective(objective) {
|
|
4672
|
+
return META_OBJECTIVE_CATEGORIES.has(objective.category);
|
|
4673
|
+
}
|
|
4508
4674
|
var OBJECTIVE_TYPES = new Set([
|
|
4509
4675
|
"flaky_test",
|
|
4510
4676
|
"lint_fix",
|
|
@@ -4725,6 +4891,67 @@ function chooseRepoTargetProfile(profiles, hints, triggerType) {
|
|
|
4725
4891
|
}
|
|
4726
4892
|
return best?.profile ?? profiles[0] ?? null;
|
|
4727
4893
|
}
|
|
4894
|
+
function chooseRepoObjectiveTargetProfile(profiles, objective) {
|
|
4895
|
+
if (profiles.length === 0)
|
|
4896
|
+
return null;
|
|
4897
|
+
const hintTokens = [...new Set([...objective.keywords, ...tokenizePath(objective.title)])];
|
|
4898
|
+
const categories = new Set([
|
|
4899
|
+
objective.category,
|
|
4900
|
+
...objective.secondary_categories
|
|
4901
|
+
]);
|
|
4902
|
+
let best = null;
|
|
4903
|
+
for (const profile of profiles) {
|
|
4904
|
+
const label = profile.label.toLowerCase();
|
|
4905
|
+
const profileTokens = new Set(profile.keywords);
|
|
4906
|
+
let score = 0;
|
|
4907
|
+
for (const token of hintTokens) {
|
|
4908
|
+
if (profileTokens.has(token))
|
|
4909
|
+
score += 3;
|
|
4910
|
+
if (label.includes(token))
|
|
4911
|
+
score += 1;
|
|
4912
|
+
}
|
|
4913
|
+
const productSurface = /(^|\/)(app|src|components|component|screens|pages|routes|styles|assets)\b/i.test(label) || /\b(client|frontend|web|ui|ux|game|screen|view|layout)\b/i.test(label);
|
|
4914
|
+
const validationSurface = /(^|\/)(__tests__|tests?|e2e|smoke|specs?)\b/i.test(label) || /\b(test|smoke|spec)\b/i.test(label);
|
|
4915
|
+
const docsSurface = /\b(readme|vision|docs?)\b/i.test(label);
|
|
4916
|
+
const scriptSurface = /(^|\/)(scripts?|tools?)\b/i.test(label);
|
|
4917
|
+
const packageSurface = /\b(package\.json|tsconfig|eslint|prettier|config)\b/i.test(label);
|
|
4918
|
+
if (categories.has("product_core") || categories.has("user_experience") || categories.has("onboarding") || categories.has("content") || categories.has("growth")) {
|
|
4919
|
+
if (productSurface)
|
|
4920
|
+
score += 5;
|
|
4921
|
+
if (/\b(game|play|screen|route|layout|index|style|component)\b/i.test(label))
|
|
4922
|
+
score += 3;
|
|
4923
|
+
if (validationSurface)
|
|
4924
|
+
score -= 7;
|
|
4925
|
+
if (docsSurface || packageSurface || scriptSurface)
|
|
4926
|
+
score -= 4;
|
|
4927
|
+
}
|
|
4928
|
+
if (categories.has("validation")) {
|
|
4929
|
+
if (validationSurface || scriptSurface)
|
|
4930
|
+
score += 5;
|
|
4931
|
+
if (productSurface)
|
|
4932
|
+
score += 1;
|
|
4933
|
+
}
|
|
4934
|
+
if (categories.has("performance")) {
|
|
4935
|
+
if (productSurface || /\b(perf|render|animation|worker|server)\b/i.test(label))
|
|
4936
|
+
score += 4;
|
|
4937
|
+
if (docsSurface)
|
|
4938
|
+
score -= 3;
|
|
4939
|
+
}
|
|
4940
|
+
if (categories.has("reliability")) {
|
|
4941
|
+
if (productSurface || scriptSurface || packageSurface || /\b(config|startup|server)\b/i.test(label)) {
|
|
4942
|
+
score += 3;
|
|
4943
|
+
}
|
|
4944
|
+
}
|
|
4945
|
+
if (categories.has("delivery_loop") || categories.has("governance") || categories.has("maintainability")) {
|
|
4946
|
+
if (scriptSurface || packageSurface || /\b(src|utils?|lib|server|shared|policy)\b/i.test(label)) {
|
|
4947
|
+
score += 3;
|
|
4948
|
+
}
|
|
4949
|
+
}
|
|
4950
|
+
if (!best || score > best.score)
|
|
4951
|
+
best = { profile, score };
|
|
4952
|
+
}
|
|
4953
|
+
return best?.profile ?? chooseRepoTargetProfile(profiles, [objective.title], "queue_health");
|
|
4954
|
+
}
|
|
4728
4955
|
function adaptCandidateShapeToRepo(params) {
|
|
4729
4956
|
const shape = params.shape;
|
|
4730
4957
|
const scopeValidation = validateScopeInvariants(shape.component_area, shape.target_paths, shape.write_globs, {
|
|
@@ -5167,12 +5394,117 @@ function maxSignalScore(snapshot, types) {
|
|
|
5167
5394
|
function maxTraitScore(snapshot, pattern) {
|
|
5168
5395
|
return clamp012(Math.max(0, ...snapshot.state_traits.filter((trait) => pattern.test(String(trait.focus ?? "")) || pattern.test(String(trait.evidence ?? "")) || pattern.test(String(trait.trait_id ?? ""))).map((trait) => asNumber(trait.score, 0))));
|
|
5169
5396
|
}
|
|
5397
|
+
function repoObjectiveWeight(params) {
|
|
5398
|
+
const rank = params.priorityRank ?? 12;
|
|
5399
|
+
const sourceBase = params.sourceBucket === "priorities" ? 0.86 : params.sourceBucket === "objectives" ? 0.78 : params.sourceBucket === "metrics" ? 0.58 : params.sourceBucket === "section" ? 0.5 : 0.42;
|
|
5400
|
+
const rankPenalty = Math.min(0.28, Math.max(0, rank - 1) * 0.045);
|
|
5401
|
+
const metaPenalty = META_OBJECTIVE_CATEGORIES.has(params.category) ? 0.08 : 0;
|
|
5402
|
+
const explicitValidationBoost = params.category === "validation" || /\b(smoke|browser|validation|test)\b/i.test(params.text) ? 0.04 : 0;
|
|
5403
|
+
return clamp012(sourceBase - rankPenalty - metaPenalty + explicitValidationBoost);
|
|
5404
|
+
}
|
|
5405
|
+
function compileRepoVisionObjectives(params) {
|
|
5406
|
+
const sectionNumbers = params.vision.section_numbers ?? [];
|
|
5407
|
+
const keyItems = params.vision.key_items;
|
|
5408
|
+
const constraints = bucketLines(keyItems, [
|
|
5409
|
+
"guardrails",
|
|
5410
|
+
"constraints",
|
|
5411
|
+
"risk_policy",
|
|
5412
|
+
"non_goals"
|
|
5413
|
+
]).slice(0, 12);
|
|
5414
|
+
const validationExpectations = [
|
|
5415
|
+
...bucketLines(keyItems, ["testing_criteria"]),
|
|
5416
|
+
...bucketLines(keyItems, ["metrics", "constraints", "risk_policy"]).filter((line) => /\b(validation|validate|test|smoke|browser|ci|check)\b/i.test(line))
|
|
5417
|
+
].slice(0, 8);
|
|
5418
|
+
const successCriteria = bucketLines(keyItems, ["metrics", "objectives", "priorities"]).slice(0, 8);
|
|
5419
|
+
const entries = [];
|
|
5420
|
+
const seen = new Set;
|
|
5421
|
+
const addEntry = (rawTitle, sourceBucket, priorityRank, explicitSectionRef) => {
|
|
5422
|
+
const title = asString2(rawTitle);
|
|
5423
|
+
if (!title)
|
|
5424
|
+
return;
|
|
5425
|
+
const key = title.toLowerCase();
|
|
5426
|
+
if (seen.has(key))
|
|
5427
|
+
return;
|
|
5428
|
+
seen.add(key);
|
|
5429
|
+
const titleCategory = categorizeVisionText(title);
|
|
5430
|
+
const contextCategory = categorizeVisionText([constraints.join(" "), validationExpectations.join(" ")].join(`
|
|
5431
|
+
`));
|
|
5432
|
+
const secondaryCategories = [
|
|
5433
|
+
...titleCategory.secondary,
|
|
5434
|
+
contextCategory.primary,
|
|
5435
|
+
...contextCategory.secondary
|
|
5436
|
+
].filter((category) => category !== "unknown" && category !== titleCategory.primary);
|
|
5437
|
+
const primaryCategory = titleCategory.primary === "unknown" && (sourceBucket === "priorities" || sourceBucket === "objectives") ? "product_core" : titleCategory.primary;
|
|
5438
|
+
const categorized = {
|
|
5439
|
+
primary: primaryCategory,
|
|
5440
|
+
secondary: [
|
|
5441
|
+
...new Set(secondaryCategories.filter((category) => category !== primaryCategory))
|
|
5442
|
+
].slice(0, 4)
|
|
5443
|
+
};
|
|
5444
|
+
const id = slugifyObjectiveId(title, `vision_objective_${entries.length + 1}`);
|
|
5445
|
+
const sectionRef = explicitSectionRef || sourceBucketSectionRef(sourceBucket, sectionNumbers) || "";
|
|
5446
|
+
const keywords = uniqueLowercaseTokens([
|
|
5447
|
+
...tokenizePath(title),
|
|
5448
|
+
categorized.primary,
|
|
5449
|
+
...categorized.secondary
|
|
5450
|
+
]);
|
|
5451
|
+
const weight = repoObjectiveWeight({
|
|
5452
|
+
sourceBucket,
|
|
5453
|
+
priorityRank,
|
|
5454
|
+
category: categorized.primary,
|
|
5455
|
+
text: title
|
|
5456
|
+
});
|
|
5457
|
+
entries.push({
|
|
5458
|
+
id,
|
|
5459
|
+
title,
|
|
5460
|
+
category: categorized.primary,
|
|
5461
|
+
secondary_categories: categorized.secondary,
|
|
5462
|
+
priority_rank: priorityRank,
|
|
5463
|
+
source_bucket: sourceBucket,
|
|
5464
|
+
section_ref: sectionRef,
|
|
5465
|
+
weight,
|
|
5466
|
+
keywords,
|
|
5467
|
+
success_criteria: successCriteria,
|
|
5468
|
+
constraints,
|
|
5469
|
+
validation_expectations: validationExpectations,
|
|
5470
|
+
evidence: [
|
|
5471
|
+
`source_bucket=${sourceBucket}`,
|
|
5472
|
+
priorityRank != null ? `priority_rank=${priorityRank}` : "priority_rank=none",
|
|
5473
|
+
`category=${categorized.primary}`,
|
|
5474
|
+
`section_ref=${sectionRef || "none"}`
|
|
5475
|
+
]
|
|
5476
|
+
});
|
|
5477
|
+
};
|
|
5478
|
+
keyItems.priorities.forEach((title, index) => addEntry(title, "priorities", index + 1));
|
|
5479
|
+
keyItems.objectives.forEach((title, index) => addEntry(title, "objectives", index + 1));
|
|
5480
|
+
keyItems.metrics.filter((title) => /\b(validation|smoke|browser|performance|reliab|startup)\b/i.test(title)).forEach((title, index) => addEntry(title, "metrics", index + 1));
|
|
5481
|
+
for (const section of params.vision.sections ?? []) {
|
|
5482
|
+
const sectionTitle = asString2(section.title);
|
|
5483
|
+
if (!sectionTitle)
|
|
5484
|
+
continue;
|
|
5485
|
+
if (/^(who this is for|the problem|scope|long-term|how decisions|get made)$/i.test(sectionTitle)) {
|
|
5486
|
+
continue;
|
|
5487
|
+
}
|
|
5488
|
+
const sectionNumber = asString2(section.number);
|
|
5489
|
+
const priorityRank = Number.isFinite(Number(sectionNumber)) ? Number(sectionNumber) : null;
|
|
5490
|
+
addEntry(sectionTitle, "section", priorityRank, sectionNumber);
|
|
5491
|
+
}
|
|
5492
|
+
return entries.sort((a, b) => {
|
|
5493
|
+
if (b.weight !== a.weight)
|
|
5494
|
+
return b.weight - a.weight;
|
|
5495
|
+
const aRank = a.priority_rank ?? Number.MAX_SAFE_INTEGER;
|
|
5496
|
+
const bRank = b.priority_rank ?? Number.MAX_SAFE_INTEGER;
|
|
5497
|
+
if (aRank !== bRank)
|
|
5498
|
+
return aRank - bRank;
|
|
5499
|
+
return a.id.localeCompare(b.id);
|
|
5500
|
+
});
|
|
5501
|
+
}
|
|
5170
5502
|
function normalizeValidationIdeas(ideas) {
|
|
5171
5503
|
const out = [];
|
|
5172
5504
|
for (const idea of ideas) {
|
|
5173
|
-
const
|
|
5174
|
-
if (
|
|
5175
|
-
out.push(
|
|
5505
|
+
const command = extractValidationCommandFromIdea(idea);
|
|
5506
|
+
if (isRepoNativeValidationCommand(command)) {
|
|
5507
|
+
out.push(command);
|
|
5176
5508
|
continue;
|
|
5177
5509
|
}
|
|
5178
5510
|
const lower = idea.toLowerCase();
|
|
@@ -5187,6 +5519,16 @@ function normalizeValidationIdeas(ideas) {
|
|
|
5187
5519
|
out.push("bun run test:root");
|
|
5188
5520
|
return [...new Set(out)].slice(0, 5);
|
|
5189
5521
|
}
|
|
5522
|
+
function extractValidationCommandFromIdea(value) {
|
|
5523
|
+
const raw = asString2(value);
|
|
5524
|
+
if (!raw)
|
|
5525
|
+
return "";
|
|
5526
|
+
const fenced = raw.match(/`([^`]+)`/)?.[1]?.trim();
|
|
5527
|
+
return (fenced || raw.replace(/^(run|execute|verify|validate|check)\s+/i, "")).trim();
|
|
5528
|
+
}
|
|
5529
|
+
function isRepoNativeValidationCommand(value) {
|
|
5530
|
+
return /^(bun|bunx|npm|npx|pnpm|yarn|node|python|python3|uv|pytest|vitest|jest|tsc|eslint|ruff|mypy|go|cargo|make|docker|pwsh|powershell|sh|bash)\b/i.test(value);
|
|
5531
|
+
}
|
|
5190
5532
|
function inferComponentAreaFromText(text, repoTargets, triggerType) {
|
|
5191
5533
|
const repoTargetMatch = chooseRepoTargetProfile(repoTargets ?? [], [text], triggerType);
|
|
5192
5534
|
if (repoTargetMatch)
|
|
@@ -5543,6 +5885,7 @@ function buildCommitHistoryBlocks(params) {
|
|
|
5543
5885
|
function buildEngineInspirationContext(params) {
|
|
5544
5886
|
const oneSentence = asString2(params.vision.one_sentence);
|
|
5545
5887
|
const keyItems = params.vision.key_items;
|
|
5888
|
+
const compiledRepoObjectives = compileRepoVisionObjectives({ vision: params.vision });
|
|
5546
5889
|
const compiledObjectives = ENGINE_OBJECTIVE_BLUEPRINTS.map((blueprint) => {
|
|
5547
5890
|
const lines = bucketLines(keyItems, blueprint.buckets);
|
|
5548
5891
|
const evidence = keywordEvidence(lines, blueprint.keywordPattern);
|
|
@@ -5709,6 +6052,7 @@ function buildEngineInspirationContext(params) {
|
|
|
5709
6052
|
}
|
|
5710
6053
|
const buildingBlocks = [...buildingBlockMap.values()].sort((a, b) => b.score - a.score);
|
|
5711
6054
|
return {
|
|
6055
|
+
compiled_repo_objectives: compiledRepoObjectives,
|
|
5712
6056
|
compiled_objectives: compiledObjectives,
|
|
5713
6057
|
opportunity_gaps: opportunityGaps,
|
|
5714
6058
|
building_blocks: buildingBlocks,
|
|
@@ -5795,6 +6139,63 @@ function inferEngineTrialFromCandidate(candidate, engineInspiration) {
|
|
|
5795
6139
|
hypothesis: fallback.hypothesis
|
|
5796
6140
|
};
|
|
5797
6141
|
}
|
|
6142
|
+
function buildRepoVisionFallbackCandidates(params) {
|
|
6143
|
+
const maxCandidates = Number.isFinite(params.maxCandidates) ? Math.max(1, Math.min(6, Math.floor(params.maxCandidates))) : 3;
|
|
6144
|
+
const sectionRefs = selectVisionSectionRefs(params.visionSectionRefs);
|
|
6145
|
+
const objectives = params.engineInspiration.compiled_repo_objectives.filter((objective) => objective.weight >= 0.42).sort((a, b) => {
|
|
6146
|
+
if (b.weight !== a.weight)
|
|
6147
|
+
return b.weight - a.weight;
|
|
6148
|
+
const aRank = a.priority_rank ?? Number.MAX_SAFE_INTEGER;
|
|
6149
|
+
const bRank = b.priority_rank ?? Number.MAX_SAFE_INTEGER;
|
|
6150
|
+
if (aRank !== bRank)
|
|
6151
|
+
return aRank - bRank;
|
|
6152
|
+
const aMeta = isMetaRepoObjective(a) ? 1 : 0;
|
|
6153
|
+
const bMeta = isMetaRepoObjective(b) ? 1 : 0;
|
|
6154
|
+
if (aMeta !== bMeta)
|
|
6155
|
+
return aMeta - bMeta;
|
|
6156
|
+
return a.id.localeCompare(b.id);
|
|
6157
|
+
}).slice(0, maxCandidates);
|
|
6158
|
+
return objectives.map((objective, idx) => {
|
|
6159
|
+
const target = chooseRepoObjectiveTargetProfile(params.repoTargets ?? [], objective);
|
|
6160
|
+
const targetPaths = target?.target_paths ?? [objective.section_ref ? `vision.md` : "README.md"];
|
|
6161
|
+
const writeGlobs = target?.write_globs ?? targetPaths;
|
|
6162
|
+
const componentArea = target?.component_area ?? (normalizeAutonomyComponentArea(pathDirname(targetPaths[0]) || targetPaths[0]) ?? "docs");
|
|
6163
|
+
const triggerType = categoryTriggerType(objective.category, params.snapshotTopSignals);
|
|
6164
|
+
const signalIds = pickSignalIdsForTrigger(params.snapshotTopSignals, triggerType);
|
|
6165
|
+
const sectionRef = objective.section_ref || sectionRefs[0] || "";
|
|
6166
|
+
const categorySummary = [
|
|
6167
|
+
objective.category,
|
|
6168
|
+
...objective.secondary_categories.slice(0, 2)
|
|
6169
|
+
].join(", ");
|
|
6170
|
+
return {
|
|
6171
|
+
id: `cand_repo_${objective.id}_${randomUUID().slice(0, 8)}`,
|
|
6172
|
+
title: `Vision objective: ${objective.title}`,
|
|
6173
|
+
objective_type: categoryObjectiveType(objective.category),
|
|
6174
|
+
problem_statement: `Advance the repo vision objective "${objective.title}" (${categorySummary}). ` + "Deliver one small, observable improvement using the repo's own product/domain language.",
|
|
6175
|
+
trigger_type: triggerType,
|
|
6176
|
+
component_area: componentArea,
|
|
6177
|
+
target_paths: targetPaths,
|
|
6178
|
+
scope: {
|
|
6179
|
+
read_anywhere: false,
|
|
6180
|
+
write_globs: writeGlobs
|
|
6181
|
+
},
|
|
6182
|
+
risk_level: "low",
|
|
6183
|
+
expected_validation: normalizeValidationIdeas(objective.validation_expectations.length > 0 ? objective.validation_expectations : ["bun run test:root"]),
|
|
6184
|
+
estimated_effort: idx === 0 ? "small" : "medium",
|
|
6185
|
+
why_now_signal_ids: signalIds,
|
|
6186
|
+
confidence: clamp012(0.5 + objective.weight * 0.45),
|
|
6187
|
+
vision_alignment_reason: `Highest repo vision category ${objective.category}; source=${objective.source_bucket}; ` + `priority=${objective.priority_rank ?? "n/a"}; section=${sectionRef || "n/a"}.`,
|
|
6188
|
+
vision_section_refs: sectionRef ? [sectionRef] : sectionRefs,
|
|
6189
|
+
feature_hypotheses: [
|
|
6190
|
+
objective.success_criteria[0] ? `Success signal: ${objective.success_criteria[0]}` : `Improve ${objective.title} without widening scope.`,
|
|
6191
|
+
objective.constraints[0] ? `Guardrail: ${objective.constraints[0]}` : "",
|
|
6192
|
+
objective.validation_expectations[0] ? `Validation expectation: ${objective.validation_expectations[0]}` : "Validate through the smallest repo-supported check."
|
|
6193
|
+
].filter(Boolean),
|
|
6194
|
+
requires_user_input: false,
|
|
6195
|
+
question_if_blocked: ""
|
|
6196
|
+
};
|
|
6197
|
+
});
|
|
6198
|
+
}
|
|
5798
6199
|
function buildEngineFallbackCandidates(params) {
|
|
5799
6200
|
const maxCandidates = Number.isFinite(params.maxCandidates) ? Math.max(1, Math.min(6, Math.floor(params.maxCandidates))) : 3;
|
|
5800
6201
|
const objectiveTitleById = new Map(params.engineInspiration.compiled_objectives.map((objective) => [
|
|
@@ -6050,6 +6451,9 @@ class RemoteBuddyAutonomousEngine {
|
|
|
6050
6451
|
const maxPhaseTimeoutMs = Math.max(this.phaseTimeoutMs("ideation"), this.phaseTimeoutMs("scoring"), this.phaseTimeoutMs("planning"));
|
|
6051
6452
|
return Math.max(this.cfg.tickIntervalMs * 3, this.cfg.ideationBudgetMs * 2 + maxPhaseTimeoutMs * 6, 30000);
|
|
6052
6453
|
}
|
|
6454
|
+
lockStaleAfterMs() {
|
|
6455
|
+
return Math.max(this.phaseTimeoutMs("ideation") + 30000, this.cfg.heartbeatLogMs * 2, 120000);
|
|
6456
|
+
}
|
|
6053
6457
|
cycleBudgetMs() {
|
|
6054
6458
|
const ideationTimeoutMs = this.phaseTimeoutMs("ideation");
|
|
6055
6459
|
const scoringTimeoutMs = this.phaseTimeoutMs("scoring");
|
|
@@ -6116,6 +6520,7 @@ class RemoteBuddyAutonomousEngine {
|
|
|
6116
6520
|
constraints: keyItems.constraints,
|
|
6117
6521
|
non_goals: keyItems.nonGoals,
|
|
6118
6522
|
metrics: keyItems.metrics,
|
|
6523
|
+
testing_criteria: keyItems.testingCriteria,
|
|
6119
6524
|
risk_policy: keyItems.riskPolicy,
|
|
6120
6525
|
operating_model: keyItems.operatingModel,
|
|
6121
6526
|
governance: keyItems.governance
|
|
@@ -6360,10 +6765,15 @@ class RemoteBuddyAutonomousEngine {
|
|
|
6360
6765
|
body: JSON.stringify({
|
|
6361
6766
|
sessionId: this.sessionId,
|
|
6362
6767
|
runId,
|
|
6363
|
-
ttlMs
|
|
6768
|
+
ttlMs,
|
|
6769
|
+
staleAfterMs: this.lockStaleAfterMs()
|
|
6364
6770
|
})
|
|
6365
6771
|
});
|
|
6366
|
-
|
|
6772
|
+
if (res.ok)
|
|
6773
|
+
return { ok: true };
|
|
6774
|
+
const payload = await res.json().catch(() => ({}));
|
|
6775
|
+
const reason = asString2(payload.reason ?? payload.message);
|
|
6776
|
+
return { ok: false, reason };
|
|
6367
6777
|
}
|
|
6368
6778
|
async renewDispatchLock(runId) {
|
|
6369
6779
|
const res = await fetch(`${this.server}/autonomy/lock/renew`, {
|
|
@@ -6666,9 +7076,10 @@ ${JSON.stringify(input.messages ?? [])}`),
|
|
|
6666
7076
|
let outcomeDetail = "not_dispatched";
|
|
6667
7077
|
try {
|
|
6668
7078
|
this.setPhase("acquire_lock");
|
|
6669
|
-
|
|
7079
|
+
const lockResult = await this.acquireDispatchLock(runId);
|
|
7080
|
+
lockAcquired = lockResult.ok;
|
|
6670
7081
|
if (!lockAcquired) {
|
|
6671
|
-
outcomeDetail = "lock_not_acquired";
|
|
7082
|
+
outcomeDetail = lockResult.reason ? compactStatusDetail(`lock_not_acquired:${lockResult.reason}`) : "lock_not_acquired";
|
|
6672
7083
|
return;
|
|
6673
7084
|
}
|
|
6674
7085
|
this.setPhase("prepare_worktree");
|
|
@@ -6840,8 +7251,16 @@ ${JSON.stringify(input.messages ?? [])}`),
|
|
|
6840
7251
|
return;
|
|
6841
7252
|
}
|
|
6842
7253
|
let rawCandidates = Array.isArray(ideationJson.candidates) ? ideationJson.candidates : [];
|
|
7254
|
+
let rawCandidatesSource = "llm";
|
|
6843
7255
|
if (rawCandidates.length === 0) {
|
|
6844
|
-
const
|
|
7256
|
+
const repoSynthesized = buildRepoVisionFallbackCandidates({
|
|
7257
|
+
engineInspiration,
|
|
7258
|
+
snapshotTopSignals: snapshot.top_signals,
|
|
7259
|
+
visionSectionRefs: visionContext.section_numbers,
|
|
7260
|
+
maxCandidates: Math.max(1, Math.min(3, this.cfg.topK)),
|
|
7261
|
+
repoTargets
|
|
7262
|
+
});
|
|
7263
|
+
const synthesized = repoSynthesized.length > 0 ? repoSynthesized : buildEngineFallbackCandidates({
|
|
6845
7264
|
engineInspiration,
|
|
6846
7265
|
snapshotTopSignals: snapshot.top_signals,
|
|
6847
7266
|
visionSectionRefs: visionContext.section_numbers,
|
|
@@ -6850,8 +7269,9 @@ ${JSON.stringify(input.messages ?? [])}`),
|
|
|
6850
7269
|
repoTargets
|
|
6851
7270
|
});
|
|
6852
7271
|
if (synthesized.length > 0) {
|
|
6853
|
-
console.log(`[RemoteBuddyAutonomousEngine] tick ${runId}: ideation returned no candidates; using ${synthesized.length} deterministic engine-inspiration fallback candidates.`);
|
|
7272
|
+
console.log(`[RemoteBuddyAutonomousEngine] tick ${runId}: ideation returned no candidates; using ${synthesized.length} deterministic ${repoSynthesized.length > 0 ? "repo-vision" : "engine-inspiration"} fallback candidates.`);
|
|
6854
7273
|
rawCandidates = synthesized;
|
|
7274
|
+
rawCandidatesSource = repoSynthesized.length > 0 ? "repo_vision_fallback" : "engine_fallback";
|
|
6855
7275
|
}
|
|
6856
7276
|
}
|
|
6857
7277
|
const normalizedCandidates = [];
|
|
@@ -6940,7 +7360,7 @@ ${JSON.stringify(input.messages ?? [])}`),
|
|
|
6940
7360
|
console.warn(`[RemoteBuddyAutonomousEngine] dropping candidate ${candidate.id}: target_paths missing in repo ${missingTargetPaths.join(", ")}`);
|
|
6941
7361
|
continue;
|
|
6942
7362
|
}
|
|
6943
|
-
if (!candidate.engine_trial) {
|
|
7363
|
+
if (!candidate.engine_trial && source !== "repo_vision_fallback") {
|
|
6944
7364
|
const inferred = inferEngineTrialFromCandidate(candidate, engineInspiration);
|
|
6945
7365
|
if (inferred) {
|
|
6946
7366
|
candidate.engine_trial = {
|
|
@@ -6952,9 +7372,16 @@ ${JSON.stringify(input.messages ?? [])}`),
|
|
|
6952
7372
|
normalizedCandidates.push(candidate);
|
|
6953
7373
|
}
|
|
6954
7374
|
};
|
|
6955
|
-
ingestRawCandidates(rawCandidates,
|
|
7375
|
+
ingestRawCandidates(rawCandidates, rawCandidatesSource);
|
|
6956
7376
|
if (normalizedCandidates.length === 0) {
|
|
6957
|
-
const
|
|
7377
|
+
const repoSynthesizedFallback = buildRepoVisionFallbackCandidates({
|
|
7378
|
+
engineInspiration,
|
|
7379
|
+
snapshotTopSignals: snapshot.top_signals,
|
|
7380
|
+
visionSectionRefs: visionContext.section_numbers,
|
|
7381
|
+
maxCandidates: Math.max(1, Math.min(3, this.cfg.topK)),
|
|
7382
|
+
repoTargets
|
|
7383
|
+
});
|
|
7384
|
+
const synthesizedFallback = repoSynthesizedFallback.length > 0 ? repoSynthesizedFallback : buildEngineFallbackCandidates({
|
|
6958
7385
|
engineInspiration,
|
|
6959
7386
|
snapshotTopSignals: snapshot.top_signals,
|
|
6960
7387
|
visionSectionRefs: visionContext.section_numbers,
|
|
@@ -6963,7 +7390,7 @@ ${JSON.stringify(input.messages ?? [])}`),
|
|
|
6963
7390
|
repoTargets
|
|
6964
7391
|
});
|
|
6965
7392
|
if (synthesizedFallback.length > 0) {
|
|
6966
|
-
ingestRawCandidates(synthesizedFallback, "engine_fallback");
|
|
7393
|
+
ingestRawCandidates(synthesizedFallback, repoSynthesizedFallback.length > 0 ? "repo_vision_fallback" : "engine_fallback");
|
|
6967
7394
|
}
|
|
6968
7395
|
}
|
|
6969
7396
|
candidatesPayload = normalizedCandidates.map((candidate) => ({
|
|
@@ -7786,7 +8213,7 @@ function ensureWriteGlobsCoverTargetPaths(targetPaths, writeGlobs) {
|
|
|
7786
8213
|
}
|
|
7787
8214
|
return { normalizedWriteGlobs, uncoveredTargets, addedGlobs };
|
|
7788
8215
|
}
|
|
7789
|
-
function buildExecutionGuidance(plan, targetPaths) {
|
|
8216
|
+
function buildExecutionGuidance(plan, targetPaths, requiredValidationSteps = []) {
|
|
7790
8217
|
const lines = [];
|
|
7791
8218
|
const targets = normalizePathHints(targetPaths.length > 0 ? targetPaths : plan.scope.write_globs ?? []);
|
|
7792
8219
|
if (targets.length > 0) {
|
|
@@ -7840,10 +8267,16 @@ function buildExecutionGuidance(plan, targetPaths) {
|
|
|
7840
8267
|
for (const step of plan.validation_steps)
|
|
7841
8268
|
lines.push(`- ${step}`);
|
|
7842
8269
|
}
|
|
8270
|
+
if (requiredValidationSteps.length > 0) {
|
|
8271
|
+
lines.push("Required vision.md testing criteria:");
|
|
8272
|
+
for (const step of requiredValidationSteps)
|
|
8273
|
+
lines.push(`- ${step}`);
|
|
8274
|
+
lines.push("- These repo-level checks are mandatory before reporting completion or publishing a PR.");
|
|
8275
|
+
}
|
|
7843
8276
|
return lines.join(`
|
|
7844
8277
|
`).trim();
|
|
7845
8278
|
}
|
|
7846
|
-
var VALIDATION_COMMAND_PREFIX = /^(git|bun|bunx|node|python|python3|uv|pytest|vitest|jest|tsc|eslint|ruff|mypy|go|cargo|make|docker|pwsh|powershell|sh|bash)\b/i;
|
|
8279
|
+
var VALIDATION_COMMAND_PREFIX = /^(git|bun|bunx|npm|npx|pnpm|yarn|node|python|python3|uv|pytest|vitest|jest|tsc|eslint|ruff|mypy|go|cargo|make|docker|pwsh|powershell|sh|bash)\b/i;
|
|
7847
8280
|
var VALIDATION_GENERIC_SAFE = /^(git\s+status\s+--porcelain|git\s+diff\b)/i;
|
|
7848
8281
|
var PATH_TOKEN_REGEX = /\b([A-Za-z0-9._/\-\\]+\.[A-Za-z0-9._-]+)\b/g;
|
|
7849
8282
|
function isCommandLikeValidationStep(step) {
|
|
@@ -7892,6 +8325,34 @@ function normalizeValidationSteps(steps, targetPaths) {
|
|
|
7892
8325
|
}
|
|
7893
8326
|
return out;
|
|
7894
8327
|
}
|
|
8328
|
+
function extractCommandFromValidationCriterion(value) {
|
|
8329
|
+
const raw = String(value ?? "").trim();
|
|
8330
|
+
if (!raw)
|
|
8331
|
+
return "";
|
|
8332
|
+
const fenced = raw.match(/`([^`]+)`/)?.[1]?.trim();
|
|
8333
|
+
const candidate = fenced || raw.replace(/^(run|execute|verify|validate|check)\s+/i, "").trim();
|
|
8334
|
+
return candidate.trim();
|
|
8335
|
+
}
|
|
8336
|
+
function extractRequiredValidationStepsFromVisionMarkdown(markdown) {
|
|
8337
|
+
const criteria = extractVisionKeyItems(markdown).testingCriteria;
|
|
8338
|
+
const out = [];
|
|
8339
|
+
const seen = new Set;
|
|
8340
|
+
for (const criterion of criteria) {
|
|
8341
|
+
const command = extractCommandFromValidationCriterion(criterion);
|
|
8342
|
+
if (!command)
|
|
8343
|
+
continue;
|
|
8344
|
+
if (!isCommandLikeValidationStep(command))
|
|
8345
|
+
continue;
|
|
8346
|
+
const key = command.toLowerCase();
|
|
8347
|
+
if (seen.has(key))
|
|
8348
|
+
continue;
|
|
8349
|
+
seen.add(key);
|
|
8350
|
+
out.push(command);
|
|
8351
|
+
if (out.length >= 12)
|
|
8352
|
+
break;
|
|
8353
|
+
}
|
|
8354
|
+
return out;
|
|
8355
|
+
}
|
|
7895
8356
|
function defaultValidationStepsForRequest(prompt, targetPaths) {
|
|
7896
8357
|
const text = prompt.toLowerCase();
|
|
7897
8358
|
const concreteTargets = targetPaths.filter((entry) => entry && entry !== ".").slice(0, 4);
|
|
@@ -7925,6 +8386,44 @@ function sanitizePlannerWorkerInstruction(workerInstruction, canonicalInstructio
|
|
|
7925
8386
|
}
|
|
7926
8387
|
return value;
|
|
7927
8388
|
}
|
|
8389
|
+
function summarizeToolRun(toolRun) {
|
|
8390
|
+
const command = toSingleLine(toolRun.commandLine, 160) || (Array.isArray(toolRun.argv) ? toSingleLine(toolRun.argv.join(" "), 160) : "");
|
|
8391
|
+
const parts = [
|
|
8392
|
+
`tool=${toSingleLine(toolRun.tool, 40) || "unknown"}`,
|
|
8393
|
+
toolRun.phase ? `phase=${toSingleLine(toolRun.phase, 60)}` : "",
|
|
8394
|
+
command ? `cmd=${command}` : "",
|
|
8395
|
+
toolRun.failureClass ? `class=${toSingleLine(toolRun.failureClass, 60)}` : "",
|
|
8396
|
+
typeof toolRun.retryable === "boolean" ? `retryable=${toolRun.retryable ? "yes" : "no"}` : "",
|
|
8397
|
+
typeof toolRun.exitCode === "number" ? `exit=${toolRun.exitCode}` : "",
|
|
8398
|
+
toolRun.remediation ? `fix=${toSingleLine(toolRun.remediation, 220)}` : ""
|
|
8399
|
+
].filter(Boolean);
|
|
8400
|
+
return parts.join(" | ");
|
|
8401
|
+
}
|
|
8402
|
+
function explainJobFailureFromToolRuns(toolRuns) {
|
|
8403
|
+
const failed = toolRuns.find((entry) => entry && entry.ok === false);
|
|
8404
|
+
if (!failed)
|
|
8405
|
+
return null;
|
|
8406
|
+
const tool = toSingleLine(failed.tool, 40) || "unknown tool";
|
|
8407
|
+
const failureClass = toSingleLine(failed.failureClass, 80) || "tool failure";
|
|
8408
|
+
const remediation = toSingleLine(failed.remediation, 260);
|
|
8409
|
+
const command = toSingleLine(failed.commandLine, 140) || (Array.isArray(failed.argv) ? toSingleLine(failed.argv.join(" "), 140) : "");
|
|
8410
|
+
return [
|
|
8411
|
+
`Latest tool failure: ${tool} reported ${failureClass}`,
|
|
8412
|
+
command ? `while running ${command}` : "",
|
|
8413
|
+
remediation ? `Recommended fix: ${remediation}` : ""
|
|
8414
|
+
].filter(Boolean).join(". ");
|
|
8415
|
+
}
|
|
8416
|
+
function formatToolRunDiagnostics(toolRuns) {
|
|
8417
|
+
const failed = toolRuns.filter((entry) => entry && entry.ok === false).slice(0, 4);
|
|
8418
|
+
if (failed.length === 0)
|
|
8419
|
+
return "";
|
|
8420
|
+
return `
|
|
8421
|
+
Tool diagnostics:
|
|
8422
|
+
\`\`\`
|
|
8423
|
+
${failed.map(summarizeToolRun).join(`
|
|
8424
|
+
`)}
|
|
8425
|
+
\`\`\``;
|
|
8426
|
+
}
|
|
7928
8427
|
function explainJobFailureFromLogs(logs, fallbackMessage, fallbackDetail) {
|
|
7929
8428
|
const lines = logs.map((row) => toSingleLine(row.message, 420)).filter(Boolean);
|
|
7930
8429
|
const joined = lines.join(`
|
|
@@ -8261,6 +8760,22 @@ class RemoteBuddyOrchestrator {
|
|
|
8261
8760
|
return [];
|
|
8262
8761
|
}
|
|
8263
8762
|
}
|
|
8763
|
+
async fetchJobToolRuns(jobId, limit = 20) {
|
|
8764
|
+
try {
|
|
8765
|
+
const res = await fetch(`${this.server}/jobs/${jobId}/tool-runs?limit=${Math.max(1, Math.min(100, limit))}`, {
|
|
8766
|
+
method: "GET",
|
|
8767
|
+
headers: this.authHeaders()
|
|
8768
|
+
});
|
|
8769
|
+
if (!res.ok)
|
|
8770
|
+
return [];
|
|
8771
|
+
const data = await res.json();
|
|
8772
|
+
if (!data.ok || !Array.isArray(data.toolRuns))
|
|
8773
|
+
return [];
|
|
8774
|
+
return data.toolRuns.filter((row) => row && typeof row.tool === "string").slice(0, 20);
|
|
8775
|
+
} catch {
|
|
8776
|
+
return [];
|
|
8777
|
+
}
|
|
8778
|
+
}
|
|
8264
8779
|
markAutonomyFeedbackEventSeen(eventId) {
|
|
8265
8780
|
const id = String(eventId ?? "").trim();
|
|
8266
8781
|
if (!id)
|
|
@@ -8405,7 +8920,10 @@ class RemoteBuddyOrchestrator {
|
|
|
8405
8920
|
return;
|
|
8406
8921
|
}
|
|
8407
8922
|
console.warn(`[RemoteBuddy] Fetching failure logs for job ${jobId}...`);
|
|
8408
|
-
const logs = await
|
|
8923
|
+
const [logs, toolRuns] = await Promise.all([
|
|
8924
|
+
this.fetchJobLogs(jobId, 80),
|
|
8925
|
+
this.fetchJobToolRuns(jobId, 20)
|
|
8926
|
+
]);
|
|
8409
8927
|
const clarificationFromLogs = extractClarificationFromJobFailure(message, detail, logs);
|
|
8410
8928
|
if (clarificationFromLogs) {
|
|
8411
8929
|
const tail2 = logs.slice(-6).map((row) => toSingleLine(row.message, 220)).filter(Boolean);
|
|
@@ -8415,9 +8933,10 @@ Recent logs:
|
|
|
8415
8933
|
${tail2.join(`
|
|
8416
8934
|
`)}
|
|
8417
8935
|
\`\`\`` : "";
|
|
8936
|
+
const toolText2 = formatToolRunDiagnostics(toolRuns);
|
|
8418
8937
|
const clarificationMsg = `WorkerPal job ${shortJob} needs clarification before making changes: ${clarificationFromLogs}
|
|
8419
8938
|
|
|
8420
|
-
` + "Reply with the missing details and I will enqueue a focused follow-up request." + tailText2;
|
|
8939
|
+
` + "Reply with the missing details and I will enqueue a focused follow-up request." + toolText2 + tailText2;
|
|
8421
8940
|
await this.assistantMessage(sessionId, clarificationMsg, {
|
|
8422
8941
|
correlationId: envelope.correlationId,
|
|
8423
8942
|
turnId: envelope.turnId,
|
|
@@ -8425,7 +8944,7 @@ ${tail2.join(`
|
|
|
8425
8944
|
});
|
|
8426
8945
|
return;
|
|
8427
8946
|
}
|
|
8428
|
-
const explanation = explainJobFailureFromLogs(logs, message, detail);
|
|
8947
|
+
const explanation = explainJobFailureFromToolRuns(toolRuns) ?? explainJobFailureFromLogs(logs, message, detail);
|
|
8429
8948
|
const tail = logs.slice(-6).map((row) => toSingleLine(row.message, 220)).filter(Boolean);
|
|
8430
8949
|
const tailText = tail.length ? `
|
|
8431
8950
|
Recent logs:
|
|
@@ -8433,7 +8952,8 @@ Recent logs:
|
|
|
8433
8952
|
${tail.join(`
|
|
8434
8953
|
`)}
|
|
8435
8954
|
\`\`\`` : "";
|
|
8436
|
-
|
|
8955
|
+
const toolText = formatToolRunDiagnostics(toolRuns);
|
|
8956
|
+
await this.assistantMessage(sessionId, `Diagnosis for job ${shortJob}: ${explanation}${toolText}${tailText}`, {
|
|
8437
8957
|
correlationId: envelope.correlationId,
|
|
8438
8958
|
turnId: envelope.turnId,
|
|
8439
8959
|
parentId: envelope.id
|
|
@@ -8708,6 +9228,17 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
8708
9228
|
}
|
|
8709
9229
|
return out;
|
|
8710
9230
|
}
|
|
9231
|
+
loadVisionRequiredValidationSteps() {
|
|
9232
|
+
const visionPath = resolve5(this.repo, "vision.md");
|
|
9233
|
+
if (!existsSync5(visionPath))
|
|
9234
|
+
return [];
|
|
9235
|
+
try {
|
|
9236
|
+
return extractRequiredValidationStepsFromVisionMarkdown(readFileSync5(visionPath, "utf8"));
|
|
9237
|
+
} catch (err) {
|
|
9238
|
+
console.warn("[RemoteBuddy] Could not read vision.md testing criteria:", err);
|
|
9239
|
+
return [];
|
|
9240
|
+
}
|
|
9241
|
+
}
|
|
8711
9242
|
getRecentContextSnapshot(sessionId = this.sessionId) {
|
|
8712
9243
|
return this.sessionContext(sessionId).slice(-RemoteBuddyOrchestrator.MAX_CONTEXT);
|
|
8713
9244
|
}
|
|
@@ -9170,7 +9701,12 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9170
9701
|
const isAnalysisFromEngine = plan.intent === "analysis" && Boolean(autonomyMetadata);
|
|
9171
9702
|
const requiresWorker = forceWorker && !isAnalysisFromEngine ? true : this.shouldForceDirectReply(prompt, plan.intent) ? false : plan.requires_worker;
|
|
9172
9703
|
console.log("[RemoteBuddy] Planner output:", { plan, targetPath, requiresWorker });
|
|
9704
|
+
let requiredValidationSteps = [];
|
|
9173
9705
|
if (requiresWorker) {
|
|
9706
|
+
requiredValidationSteps = this.loadVisionRequiredValidationSteps();
|
|
9707
|
+
if (requiredValidationSteps.length > 0) {
|
|
9708
|
+
console.log(`[RemoteBuddy] Loaded ${requiredValidationSteps.length} required validation step(s) from vision.md testing criteria.`);
|
|
9709
|
+
}
|
|
9174
9710
|
const scopeCoverage = ensureWriteGlobsCoverTargetPaths(targetPaths, plan.scope.write_globs);
|
|
9175
9711
|
if (scopeCoverage.normalizedWriteGlobs.length > 0) {
|
|
9176
9712
|
plan.scope.write_globs = scopeCoverage.normalizedWriteGlobs;
|
|
@@ -9220,7 +9756,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9220
9756
|
}
|
|
9221
9757
|
const canonicalInstruction = prompt.trim();
|
|
9222
9758
|
const rawPlannerInstruction = sanitizePlannerWorkerInstruction(String(plan.worker_instruction ?? ""), canonicalInstruction);
|
|
9223
|
-
const executionGuidance = buildExecutionGuidance(plan, targetPaths);
|
|
9759
|
+
const executionGuidance = buildExecutionGuidance(plan, targetPaths, requiredValidationSteps);
|
|
9224
9760
|
const plannerWorkerInstruction = [rawPlannerInstruction, executionGuidance].filter(Boolean).join(`
|
|
9225
9761
|
|
|
9226
9762
|
`).trim();
|
|
@@ -9326,6 +9862,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9326
9862
|
} : {},
|
|
9327
9863
|
acceptanceCriteria: plan.acceptance_criteria,
|
|
9328
9864
|
validationSteps: plan.validation_steps,
|
|
9865
|
+
...requiredValidationSteps.length > 0 ? { requiredValidationSteps } : {},
|
|
9329
9866
|
queuePriority: priority,
|
|
9330
9867
|
queueWaitBudgetMs,
|
|
9331
9868
|
executionBudgetMs,
|
|
@@ -9655,6 +10192,7 @@ if (import.meta.main) {
|
|
|
9655
10192
|
});
|
|
9656
10193
|
}
|
|
9657
10194
|
export {
|
|
10195
|
+
extractRequiredValidationStepsFromVisionMarkdown,
|
|
9658
10196
|
buildTaskExecuteDedupeKey,
|
|
9659
10197
|
RemoteBuddyOrchestrator
|
|
9660
10198
|
};
|