@pushpalsdev/cli 1.0.79 → 1.0.81

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.
Files changed (24) hide show
  1. package/dist/pushpals-cli.js +66 -3
  2. package/monitor-ui/+not-found.html +1 -1
  3. package/monitor-ui/_expo/static/js/web/{entry-c6862f701ea52ccf8692a6c9e749af5c.js → entry-e66b4de45f75e702ac16916082bcc9a5.js} +172 -171
  4. package/monitor-ui/_expo/static/js/web/{index-6013f9ebc87a963a55bb9137af1a5a06.js → index-ec13ec62e2b37ed3c5f6d324ef6784e1.js} +4 -4
  5. package/monitor-ui/_sitemap.html +1 -1
  6. package/monitor-ui/index.html +1 -1
  7. package/monitor-ui/modal.html +1 -1
  8. package/package.json +1 -1
  9. package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +5 -3
  10. package/runtime/prompts/workerpals/openai_codex_default_system_prompt.md +1 -0
  11. package/runtime/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
  12. package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +1 -0
  13. package/runtime/sandbox/.pushpals-remotebuddy-fallback.js +560 -23
  14. package/runtime/sandbox/apps/workerpals/src/execute_job.ts +282 -33
  15. package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +113 -2
  16. package/runtime/sandbox/packages/shared/src/client_preflight.ts +2 -0
  17. package/runtime/sandbox/packages/shared/src/config.ts +1 -6
  18. package/runtime/sandbox/packages/shared/src/index.ts +19 -0
  19. package/runtime/sandbox/packages/shared/src/tooling.ts +422 -0
  20. package/runtime/sandbox/packages/shared/src/vision.ts +12 -0
  21. package/runtime/sandbox/prompts/workerpals/openai_codex_default_system_prompt.md +1 -0
  22. package/runtime/sandbox/prompts/workerpals/openai_codex_runtime_policy_appendix.md +1 -0
  23. package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +1 -0
  24. package/runtime/vision.example.md +125 -122
@@ -2,7 +2,7 @@
2
2
  // @bun
3
3
 
4
4
  // apps/remotebuddy/src/remotebuddy_main.ts
5
- import { randomUUID as randomUUID2 } from "crypto";
5
+ import { randomUUID as randomUUID3 } from "crypto";
6
6
  import { Database as Database3 } from "bun:sqlite";
7
7
 
8
8
  // apps/remotebuddy/src/llm.ts
@@ -1088,7 +1088,7 @@ function loadPushPalsConfig(options = {}) {
1088
1088
  const canonical = coerceAutonomyComponentConfigKey(rawKey);
1089
1089
  if (!canonical)
1090
1090
  continue;
1091
- const parsed = typeof rawValue === "number" ? rawValue : typeof rawValue === "string" ? Number.parseInt(rawValue.trim(), 10) : Number.NaN;
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
@@ -4059,7 +4076,7 @@ class PersistentSessionMemory {
4059
4076
  }
4060
4077
 
4061
4078
  // apps/remotebuddy/src/remotebuddy_main.ts
4062
- import { existsSync as existsSync5, mkdirSync as mkdirSync2 } from "fs";
4079
+ import { existsSync as existsSync5, mkdirSync as mkdirSync2, readFileSync as readFileSync5 } from "fs";
4063
4080
  import { resolve as resolve5 } from "path";
4064
4081
 
4065
4082
  // apps/remotebuddy/src/autonomous_engine.ts
@@ -4516,6 +4533,144 @@ function uniqueLowercaseTokens(values, max = 24) {
4516
4533
  }
4517
4534
  return out;
4518
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
+ }
4519
4674
  var OBJECTIVE_TYPES = new Set([
4520
4675
  "flaky_test",
4521
4676
  "lint_fix",
@@ -4736,6 +4891,67 @@ function chooseRepoTargetProfile(profiles, hints, triggerType) {
4736
4891
  }
4737
4892
  return best?.profile ?? profiles[0] ?? null;
4738
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
+ }
4739
4955
  function adaptCandidateShapeToRepo(params) {
4740
4956
  const shape = params.shape;
4741
4957
  const scopeValidation = validateScopeInvariants(shape.component_area, shape.target_paths, shape.write_globs, {
@@ -5178,12 +5394,117 @@ function maxSignalScore(snapshot, types) {
5178
5394
  function maxTraitScore(snapshot, pattern) {
5179
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))));
5180
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
+ }
5181
5502
  function normalizeValidationIdeas(ideas) {
5182
5503
  const out = [];
5183
5504
  for (const idea of ideas) {
5184
- const canonical = canonicalizeValidationCommandForBun(idea);
5185
- if (canonical.startsWith("bun ")) {
5186
- out.push(canonical);
5505
+ const command = extractValidationCommandFromIdea(idea);
5506
+ if (isRepoNativeValidationCommand(command)) {
5507
+ out.push(command);
5187
5508
  continue;
5188
5509
  }
5189
5510
  const lower = idea.toLowerCase();
@@ -5198,6 +5519,16 @@ function normalizeValidationIdeas(ideas) {
5198
5519
  out.push("bun run test:root");
5199
5520
  return [...new Set(out)].slice(0, 5);
5200
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
+ }
5201
5532
  function inferComponentAreaFromText(text, repoTargets, triggerType) {
5202
5533
  const repoTargetMatch = chooseRepoTargetProfile(repoTargets ?? [], [text], triggerType);
5203
5534
  if (repoTargetMatch)
@@ -5554,6 +5885,7 @@ function buildCommitHistoryBlocks(params) {
5554
5885
  function buildEngineInspirationContext(params) {
5555
5886
  const oneSentence = asString2(params.vision.one_sentence);
5556
5887
  const keyItems = params.vision.key_items;
5888
+ const compiledRepoObjectives = compileRepoVisionObjectives({ vision: params.vision });
5557
5889
  const compiledObjectives = ENGINE_OBJECTIVE_BLUEPRINTS.map((blueprint) => {
5558
5890
  const lines = bucketLines(keyItems, blueprint.buckets);
5559
5891
  const evidence = keywordEvidence(lines, blueprint.keywordPattern);
@@ -5720,6 +6052,7 @@ function buildEngineInspirationContext(params) {
5720
6052
  }
5721
6053
  const buildingBlocks = [...buildingBlockMap.values()].sort((a, b) => b.score - a.score);
5722
6054
  return {
6055
+ compiled_repo_objectives: compiledRepoObjectives,
5723
6056
  compiled_objectives: compiledObjectives,
5724
6057
  opportunity_gaps: opportunityGaps,
5725
6058
  building_blocks: buildingBlocks,
@@ -5806,6 +6139,63 @@ function inferEngineTrialFromCandidate(candidate, engineInspiration) {
5806
6139
  hypothesis: fallback.hypothesis
5807
6140
  };
5808
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
+ }
5809
6199
  function buildEngineFallbackCandidates(params) {
5810
6200
  const maxCandidates = Number.isFinite(params.maxCandidates) ? Math.max(1, Math.min(6, Math.floor(params.maxCandidates))) : 3;
5811
6201
  const objectiveTitleById = new Map(params.engineInspiration.compiled_objectives.map((objective) => [
@@ -6130,6 +6520,7 @@ class RemoteBuddyAutonomousEngine {
6130
6520
  constraints: keyItems.constraints,
6131
6521
  non_goals: keyItems.nonGoals,
6132
6522
  metrics: keyItems.metrics,
6523
+ testing_criteria: keyItems.testingCriteria,
6133
6524
  risk_policy: keyItems.riskPolicy,
6134
6525
  operating_model: keyItems.operatingModel,
6135
6526
  governance: keyItems.governance
@@ -6860,8 +7251,16 @@ ${JSON.stringify(input.messages ?? [])}`),
6860
7251
  return;
6861
7252
  }
6862
7253
  let rawCandidates = Array.isArray(ideationJson.candidates) ? ideationJson.candidates : [];
7254
+ let rawCandidatesSource = "llm";
6863
7255
  if (rawCandidates.length === 0) {
6864
- const synthesized = buildEngineFallbackCandidates({
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({
6865
7264
  engineInspiration,
6866
7265
  snapshotTopSignals: snapshot.top_signals,
6867
7266
  visionSectionRefs: visionContext.section_numbers,
@@ -6870,8 +7269,9 @@ ${JSON.stringify(input.messages ?? [])}`),
6870
7269
  repoTargets
6871
7270
  });
6872
7271
  if (synthesized.length > 0) {
6873
- 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.`);
6874
7273
  rawCandidates = synthesized;
7274
+ rawCandidatesSource = repoSynthesized.length > 0 ? "repo_vision_fallback" : "engine_fallback";
6875
7275
  }
6876
7276
  }
6877
7277
  const normalizedCandidates = [];
@@ -6960,7 +7360,7 @@ ${JSON.stringify(input.messages ?? [])}`),
6960
7360
  console.warn(`[RemoteBuddyAutonomousEngine] dropping candidate ${candidate.id}: target_paths missing in repo ${missingTargetPaths.join(", ")}`);
6961
7361
  continue;
6962
7362
  }
6963
- if (!candidate.engine_trial) {
7363
+ if (!candidate.engine_trial && source !== "repo_vision_fallback") {
6964
7364
  const inferred = inferEngineTrialFromCandidate(candidate, engineInspiration);
6965
7365
  if (inferred) {
6966
7366
  candidate.engine_trial = {
@@ -6972,9 +7372,16 @@ ${JSON.stringify(input.messages ?? [])}`),
6972
7372
  normalizedCandidates.push(candidate);
6973
7373
  }
6974
7374
  };
6975
- ingestRawCandidates(rawCandidates, "llm");
7375
+ ingestRawCandidates(rawCandidates, rawCandidatesSource);
6976
7376
  if (normalizedCandidates.length === 0) {
6977
- const synthesizedFallback = buildEngineFallbackCandidates({
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({
6978
7385
  engineInspiration,
6979
7386
  snapshotTopSignals: snapshot.top_signals,
6980
7387
  visionSectionRefs: visionContext.section_numbers,
@@ -6983,7 +7390,7 @@ ${JSON.stringify(input.messages ?? [])}`),
6983
7390
  repoTargets
6984
7391
  });
6985
7392
  if (synthesizedFallback.length > 0) {
6986
- ingestRawCandidates(synthesizedFallback, "engine_fallback");
7393
+ ingestRawCandidates(synthesizedFallback, repoSynthesizedFallback.length > 0 ? "repo_vision_fallback" : "engine_fallback");
6987
7394
  }
6988
7395
  }
6989
7396
  candidatesPayload = normalizedCandidates.map((candidate) => ({
@@ -7513,6 +7920,14 @@ Scope:
7513
7920
  }
7514
7921
 
7515
7922
  // apps/remotebuddy/src/worker_spawn.ts
7923
+ import { randomUUID as randomUUID2 } from "crypto";
7924
+ function createWorkerPalId(options = {}) {
7925
+ const randomPart = String(options.randomId ?? randomUUID2()).replace(/[^a-z0-9]/gi, "");
7926
+ const timePart = Math.max(0, Math.floor(options.nowMs ?? Date.now())).toString(36);
7927
+ const pidPart = Math.max(0, Math.floor(options.processId ?? process.pid)).toString(36);
7928
+ const suffix = `${timePart}${pidPart}${randomPart}`.toLowerCase().slice(0, 12);
7929
+ return `workerpal-${suffix || "worker"}`;
7930
+ }
7516
7931
  function resolveWorkerStartupTimeoutMs(options) {
7517
7932
  const configuredMs = Math.max(1000, Math.floor(options.configuredMs || 0));
7518
7933
  if (!options.docker) {
@@ -7806,7 +8221,7 @@ function ensureWriteGlobsCoverTargetPaths(targetPaths, writeGlobs) {
7806
8221
  }
7807
8222
  return { normalizedWriteGlobs, uncoveredTargets, addedGlobs };
7808
8223
  }
7809
- function buildExecutionGuidance(plan, targetPaths) {
8224
+ function buildExecutionGuidance(plan, targetPaths, requiredValidationSteps = []) {
7810
8225
  const lines = [];
7811
8226
  const targets = normalizePathHints(targetPaths.length > 0 ? targetPaths : plan.scope.write_globs ?? []);
7812
8227
  if (targets.length > 0) {
@@ -7860,10 +8275,16 @@ function buildExecutionGuidance(plan, targetPaths) {
7860
8275
  for (const step of plan.validation_steps)
7861
8276
  lines.push(`- ${step}`);
7862
8277
  }
8278
+ if (requiredValidationSteps.length > 0) {
8279
+ lines.push("Required vision.md testing criteria:");
8280
+ for (const step of requiredValidationSteps)
8281
+ lines.push(`- ${step}`);
8282
+ lines.push("- These repo-level checks are mandatory before reporting completion or publishing a PR.");
8283
+ }
7863
8284
  return lines.join(`
7864
8285
  `).trim();
7865
8286
  }
7866
- 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;
8287
+ 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;
7867
8288
  var VALIDATION_GENERIC_SAFE = /^(git\s+status\s+--porcelain|git\s+diff\b)/i;
7868
8289
  var PATH_TOKEN_REGEX = /\b([A-Za-z0-9._/\-\\]+\.[A-Za-z0-9._-]+)\b/g;
7869
8290
  function isCommandLikeValidationStep(step) {
@@ -7912,6 +8333,34 @@ function normalizeValidationSteps(steps, targetPaths) {
7912
8333
  }
7913
8334
  return out;
7914
8335
  }
8336
+ function extractCommandFromValidationCriterion(value) {
8337
+ const raw = String(value ?? "").trim();
8338
+ if (!raw)
8339
+ return "";
8340
+ const fenced = raw.match(/`([^`]+)`/)?.[1]?.trim();
8341
+ const candidate = fenced || raw.replace(/^(run|execute|verify|validate|check)\s+/i, "").trim();
8342
+ return candidate.trim();
8343
+ }
8344
+ function extractRequiredValidationStepsFromVisionMarkdown(markdown) {
8345
+ const criteria = extractVisionKeyItems(markdown).testingCriteria;
8346
+ const out = [];
8347
+ const seen = new Set;
8348
+ for (const criterion of criteria) {
8349
+ const command = extractCommandFromValidationCriterion(criterion);
8350
+ if (!command)
8351
+ continue;
8352
+ if (!isCommandLikeValidationStep(command))
8353
+ continue;
8354
+ const key = command.toLowerCase();
8355
+ if (seen.has(key))
8356
+ continue;
8357
+ seen.add(key);
8358
+ out.push(command);
8359
+ if (out.length >= 12)
8360
+ break;
8361
+ }
8362
+ return out;
8363
+ }
7915
8364
  function defaultValidationStepsForRequest(prompt, targetPaths) {
7916
8365
  const text = prompt.toLowerCase();
7917
8366
  const concreteTargets = targetPaths.filter((entry) => entry && entry !== ".").slice(0, 4);
@@ -7945,6 +8394,44 @@ function sanitizePlannerWorkerInstruction(workerInstruction, canonicalInstructio
7945
8394
  }
7946
8395
  return value;
7947
8396
  }
8397
+ function summarizeToolRun(toolRun) {
8398
+ const command = toSingleLine(toolRun.commandLine, 160) || (Array.isArray(toolRun.argv) ? toSingleLine(toolRun.argv.join(" "), 160) : "");
8399
+ const parts = [
8400
+ `tool=${toSingleLine(toolRun.tool, 40) || "unknown"}`,
8401
+ toolRun.phase ? `phase=${toSingleLine(toolRun.phase, 60)}` : "",
8402
+ command ? `cmd=${command}` : "",
8403
+ toolRun.failureClass ? `class=${toSingleLine(toolRun.failureClass, 60)}` : "",
8404
+ typeof toolRun.retryable === "boolean" ? `retryable=${toolRun.retryable ? "yes" : "no"}` : "",
8405
+ typeof toolRun.exitCode === "number" ? `exit=${toolRun.exitCode}` : "",
8406
+ toolRun.remediation ? `fix=${toSingleLine(toolRun.remediation, 220)}` : ""
8407
+ ].filter(Boolean);
8408
+ return parts.join(" | ");
8409
+ }
8410
+ function explainJobFailureFromToolRuns(toolRuns) {
8411
+ const failed = toolRuns.find((entry) => entry && entry.ok === false);
8412
+ if (!failed)
8413
+ return null;
8414
+ const tool = toSingleLine(failed.tool, 40) || "unknown tool";
8415
+ const failureClass = toSingleLine(failed.failureClass, 80) || "tool failure";
8416
+ const remediation = toSingleLine(failed.remediation, 260);
8417
+ const command = toSingleLine(failed.commandLine, 140) || (Array.isArray(failed.argv) ? toSingleLine(failed.argv.join(" "), 140) : "");
8418
+ return [
8419
+ `Latest tool failure: ${tool} reported ${failureClass}`,
8420
+ command ? `while running ${command}` : "",
8421
+ remediation ? `Recommended fix: ${remediation}` : ""
8422
+ ].filter(Boolean).join(". ");
8423
+ }
8424
+ function formatToolRunDiagnostics(toolRuns) {
8425
+ const failed = toolRuns.filter((entry) => entry && entry.ok === false).slice(0, 4);
8426
+ if (failed.length === 0)
8427
+ return "";
8428
+ return `
8429
+ Tool diagnostics:
8430
+ \`\`\`
8431
+ ${failed.map(summarizeToolRun).join(`
8432
+ `)}
8433
+ \`\`\``;
8434
+ }
7948
8435
  function explainJobFailureFromLogs(logs, fallbackMessage, fallbackDetail) {
7949
8436
  const lines = logs.map((row) => toSingleLine(row.message, 420)).filter(Boolean);
7950
8437
  const joined = lines.join(`
@@ -8058,6 +8545,7 @@ class RemoteBuddyOrchestrator {
8058
8545
  autonomyConfigPollTimer = null;
8059
8546
  managedWorkers = new Map;
8060
8547
  workerSpawnInFlight = null;
8548
+ workerStartupPrewarmInFlight = null;
8061
8549
  workerSpawnCooldownUntil = 0;
8062
8550
  workerSpawnBackoffMs;
8063
8551
  workerAutoscalePollMs;
@@ -8281,6 +8769,22 @@ class RemoteBuddyOrchestrator {
8281
8769
  return [];
8282
8770
  }
8283
8771
  }
8772
+ async fetchJobToolRuns(jobId, limit = 20) {
8773
+ try {
8774
+ const res = await fetch(`${this.server}/jobs/${jobId}/tool-runs?limit=${Math.max(1, Math.min(100, limit))}`, {
8775
+ method: "GET",
8776
+ headers: this.authHeaders()
8777
+ });
8778
+ if (!res.ok)
8779
+ return [];
8780
+ const data = await res.json();
8781
+ if (!data.ok || !Array.isArray(data.toolRuns))
8782
+ return [];
8783
+ return data.toolRuns.filter((row) => row && typeof row.tool === "string").slice(0, 20);
8784
+ } catch {
8785
+ return [];
8786
+ }
8787
+ }
8284
8788
  markAutonomyFeedbackEventSeen(eventId) {
8285
8789
  const id = String(eventId ?? "").trim();
8286
8790
  if (!id)
@@ -8425,7 +8929,10 @@ class RemoteBuddyOrchestrator {
8425
8929
  return;
8426
8930
  }
8427
8931
  console.warn(`[RemoteBuddy] Fetching failure logs for job ${jobId}...`);
8428
- const logs = await this.fetchJobLogs(jobId, 80);
8932
+ const [logs, toolRuns] = await Promise.all([
8933
+ this.fetchJobLogs(jobId, 80),
8934
+ this.fetchJobToolRuns(jobId, 20)
8935
+ ]);
8429
8936
  const clarificationFromLogs = extractClarificationFromJobFailure(message, detail, logs);
8430
8937
  if (clarificationFromLogs) {
8431
8938
  const tail2 = logs.slice(-6).map((row) => toSingleLine(row.message, 220)).filter(Boolean);
@@ -8435,9 +8942,10 @@ Recent logs:
8435
8942
  ${tail2.join(`
8436
8943
  `)}
8437
8944
  \`\`\`` : "";
8945
+ const toolText2 = formatToolRunDiagnostics(toolRuns);
8438
8946
  const clarificationMsg = `WorkerPal job ${shortJob} needs clarification before making changes: ${clarificationFromLogs}
8439
8947
 
8440
- ` + "Reply with the missing details and I will enqueue a focused follow-up request." + tailText2;
8948
+ ` + "Reply with the missing details and I will enqueue a focused follow-up request." + toolText2 + tailText2;
8441
8949
  await this.assistantMessage(sessionId, clarificationMsg, {
8442
8950
  correlationId: envelope.correlationId,
8443
8951
  turnId: envelope.turnId,
@@ -8445,7 +8953,7 @@ ${tail2.join(`
8445
8953
  });
8446
8954
  return;
8447
8955
  }
8448
- const explanation = explainJobFailureFromLogs(logs, message, detail);
8956
+ const explanation = explainJobFailureFromToolRuns(toolRuns) ?? explainJobFailureFromLogs(logs, message, detail);
8449
8957
  const tail = logs.slice(-6).map((row) => toSingleLine(row.message, 220)).filter(Boolean);
8450
8958
  const tailText = tail.length ? `
8451
8959
  Recent logs:
@@ -8453,7 +8961,8 @@ Recent logs:
8453
8961
  ${tail.join(`
8454
8962
  `)}
8455
8963
  \`\`\`` : "";
8456
- await this.assistantMessage(sessionId, `Diagnosis for job ${shortJob}: ${explanation}${tailText}`, {
8964
+ const toolText = formatToolRunDiagnostics(toolRuns);
8965
+ await this.assistantMessage(sessionId, `Diagnosis for job ${shortJob}: ${explanation}${toolText}${tailText}`, {
8457
8966
  correlationId: envelope.correlationId,
8458
8967
  turnId: envelope.turnId,
8459
8968
  parentId: envelope.id
@@ -8728,6 +9237,17 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
8728
9237
  }
8729
9238
  return out;
8730
9239
  }
9240
+ loadVisionRequiredValidationSteps() {
9241
+ const visionPath = resolve5(this.repo, "vision.md");
9242
+ if (!existsSync5(visionPath))
9243
+ return [];
9244
+ try {
9245
+ return extractRequiredValidationStepsFromVisionMarkdown(readFileSync5(visionPath, "utf8"));
9246
+ } catch (err) {
9247
+ console.warn("[RemoteBuddy] Could not read vision.md testing criteria:", err);
9248
+ return [];
9249
+ }
9250
+ }
8731
9251
  getRecentContextSnapshot(sessionId = this.sessionId) {
8732
9252
  return this.sessionContext(sessionId).slice(-RemoteBuddyOrchestrator.MAX_CONTEXT);
8733
9253
  }
@@ -9028,7 +9548,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9028
9548
  }
9029
9549
  const spawnPromise = (async () => {
9030
9550
  this.workerpalsUnavailableReason = null;
9031
- const workerId = `workerpal-${randomUUID2().substring(0, 8)}`;
9551
+ const workerId = createWorkerPalId();
9032
9552
  const cmd = this.buildWorkerSpawnCommand(workerId);
9033
9553
  console.log(`[RemoteBuddy] Spawning WorkerPal ${workerId} (${this.managedWorkers.size + 1}/${this.maxWorkers})`);
9034
9554
  try {
@@ -9114,6 +9634,16 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9114
9634
  }
9115
9635
  console.warn(`[RemoteBuddy] ${this.currentWorkerUnavailableReason()}`);
9116
9636
  }
9637
+ startWorkerCapacityPrewarmOnStartup() {
9638
+ if (this.workerStartupPrewarmInFlight || this.disposed)
9639
+ return;
9640
+ this.workerStartupPrewarmInFlight = this.ensureWorkerCapacityOnStartup().catch((err) => {
9641
+ this.workerpalsUnavailableReason = `WorkerPal startup prewarm failed: ${String(err)}`;
9642
+ console.warn(`[RemoteBuddy] ${this.workerpalsUnavailableReason}`);
9643
+ }).finally(() => {
9644
+ this.workerStartupPrewarmInFlight = null;
9645
+ });
9646
+ }
9117
9647
  async selectTargetWorkerForJob() {
9118
9648
  const workers = await this.fetchWorkers();
9119
9649
  const idleNow = this.pickIdleWorker(workers);
@@ -9162,7 +9692,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9162
9692
  }
9163
9693
  const priority = normalizeRequestPriority(request.priority);
9164
9694
  const queueWaitBudgetMs = Math.max(5000, Number.isFinite(Number(request.queueWaitBudgetMs)) ? Number(request.queueWaitBudgetMs) : priority === "interactive" ? 20000 : priority === "background" ? 240000 : 90000);
9165
- const turnId = randomUUID2();
9695
+ const turnId = randomUUID3();
9166
9696
  const eventFrom = autonomyMetadata ? `agent:${this.agentId}/autonomy` : undefined;
9167
9697
  const planningContext = this.buildPlanningContext(priority, requestSessionId);
9168
9698
  this.rememberPersistentMemory("request", `priority=${priority} prompt=${toSingleLine(prompt, 520)}`, requestId, requestSessionId);
@@ -9190,7 +9720,12 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9190
9720
  const isAnalysisFromEngine = plan.intent === "analysis" && Boolean(autonomyMetadata);
9191
9721
  const requiresWorker = forceWorker && !isAnalysisFromEngine ? true : this.shouldForceDirectReply(prompt, plan.intent) ? false : plan.requires_worker;
9192
9722
  console.log("[RemoteBuddy] Planner output:", { plan, targetPath, requiresWorker });
9723
+ let requiredValidationSteps = [];
9193
9724
  if (requiresWorker) {
9725
+ requiredValidationSteps = this.loadVisionRequiredValidationSteps();
9726
+ if (requiredValidationSteps.length > 0) {
9727
+ console.log(`[RemoteBuddy] Loaded ${requiredValidationSteps.length} required validation step(s) from vision.md testing criteria.`);
9728
+ }
9194
9729
  const scopeCoverage = ensureWriteGlobsCoverTargetPaths(targetPaths, plan.scope.write_globs);
9195
9730
  if (scopeCoverage.normalizedWriteGlobs.length > 0) {
9196
9731
  plan.scope.write_globs = scopeCoverage.normalizedWriteGlobs;
@@ -9240,7 +9775,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9240
9775
  }
9241
9776
  const canonicalInstruction = prompt.trim();
9242
9777
  const rawPlannerInstruction = sanitizePlannerWorkerInstruction(String(plan.worker_instruction ?? ""), canonicalInstruction);
9243
- const executionGuidance = buildExecutionGuidance(plan, targetPaths);
9778
+ const executionGuidance = buildExecutionGuidance(plan, targetPaths, requiredValidationSteps);
9244
9779
  const plannerWorkerInstruction = [rawPlannerInstruction, executionGuidance].filter(Boolean).join(`
9245
9780
 
9246
9781
  `).trim();
@@ -9286,7 +9821,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9286
9821
  this.rememberPersistentMemory("decision", `completed_without_worker intent=${plan.intent} lane=deterministic`, requestId, requestSessionId);
9287
9822
  return;
9288
9823
  }
9289
- const taskId = randomUUID2();
9824
+ const taskId = randomUUID3();
9290
9825
  const targetWorkerId = await this.selectTargetWorkerForJob();
9291
9826
  if (!targetWorkerId) {
9292
9827
  const onlineWorkers = this.onlineWorkers(await this.fetchWorkers());
@@ -9346,6 +9881,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9346
9881
  } : {},
9347
9882
  acceptanceCriteria: plan.acceptance_criteria,
9348
9883
  validationSteps: plan.validation_steps,
9884
+ ...requiredValidationSteps.length > 0 ? { requiredValidationSteps } : {},
9349
9885
  queuePriority: priority,
9350
9886
  queueWaitBudgetMs,
9351
9887
  executionBudgetMs,
@@ -9662,7 +10198,7 @@ async function main() {
9662
10198
  await orchestrator.emitStartupStatus();
9663
10199
  orchestrator.startStatusHeartbeat();
9664
10200
  orchestrator.startSessionEventMonitor();
9665
- await orchestrator.ensureWorkerCapacityOnStartup();
10201
+ orchestrator.startWorkerCapacityPrewarmOnStartup();
9666
10202
  orchestrator.startAutonomy();
9667
10203
  orchestrator.startAutonomyRuntimeConfigPolling();
9668
10204
  const pollMs = CONFIG.remotebuddy.pollMs;
@@ -9675,6 +10211,7 @@ if (import.meta.main) {
9675
10211
  });
9676
10212
  }
9677
10213
  export {
10214
+ extractRequiredValidationStepsFromVisionMarkdown,
9678
10215
  buildTaskExecuteDedupeKey,
9679
10216
  RemoteBuddyOrchestrator
9680
10217
  };