@pushpalsdev/cli 1.0.86 → 1.0.94

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 (27) hide show
  1. package/dist/pushpals-cli.js +1 -1
  2. package/package.json +2 -2
  3. package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +2 -1
  4. package/runtime/prompts/remotebuddy/autonomy_planning_system_prompt.md +1 -1
  5. package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +4 -4
  6. package/runtime/prompts/workerpals/miniswe_completion_requirement.md +1 -1
  7. package/runtime/prompts/workerpals/miniswe_explicit_targets_block.md +1 -1
  8. package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +4 -1
  9. package/runtime/prompts/workerpals/openhands_minimal_system_prompt.j2 +3 -1
  10. package/runtime/prompts/workerpals/openhands_task_execute_system_prompt.md +2 -1
  11. package/runtime/prompts/workerpals/workerpals_system_prompt.md +2 -2
  12. package/runtime/sandbox/.pushpals-remotebuddy-fallback.js +248 -98
  13. package/runtime/sandbox/apps/workerpals/src/backends/miniswe/miniswe_executor.py +5 -34
  14. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/openai_codex_executor.py +219 -130
  15. package/runtime/sandbox/apps/workerpals/src/backends/openai_codex/test_openai_codex_runtime_config.py +57 -0
  16. package/runtime/sandbox/apps/workerpals/src/backends/openhands/openhands_executor.py +3 -2
  17. package/runtime/sandbox/apps/workerpals/src/execute_job.ts +142 -134
  18. package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +70 -25
  19. package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +14 -8
  20. package/runtime/sandbox/packages/shared/src/communication.ts +4 -1
  21. package/runtime/sandbox/packages/shared/src/config.ts +1 -1
  22. package/runtime/sandbox/prompts/workerpals/miniswe_completion_requirement.md +1 -1
  23. package/runtime/sandbox/prompts/workerpals/miniswe_explicit_targets_block.md +1 -1
  24. package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +4 -1
  25. package/runtime/sandbox/prompts/workerpals/openhands_minimal_system_prompt.j2 +3 -1
  26. package/runtime/sandbox/prompts/workerpals/openhands_task_execute_system_prompt.md +2 -1
  27. package/runtime/sandbox/prompts/workerpals/workerpals_system_prompt.md +2 -2
@@ -86,11 +86,13 @@ class CommunicationManager {
86
86
  sessionId;
87
87
  from;
88
88
  authToken;
89
+ fetchImpl;
89
90
  constructor(opts) {
90
91
  this.serverUrl = opts.serverUrl;
91
92
  this.sessionId = opts.sessionId;
92
93
  this.from = opts.from;
93
94
  this.authToken = opts.authToken ?? null;
95
+ this.fetchImpl = opts.fetchImpl ?? fetch;
94
96
  }
95
97
  headers() {
96
98
  const headers = { "Content-Type": "application/json" };
@@ -132,7 +134,7 @@ class CommunicationManager {
132
134
  body.turnId = meta.turnId;
133
135
  if (meta.parentId)
134
136
  body.parentId = meta.parentId;
135
- const response = await fetch(this.commandUrl(sessionId), {
137
+ const response = await this.fetchImpl(this.commandUrl(sessionId), {
136
138
  method: "POST",
137
139
  headers: this.headers(),
138
140
  body: JSON.stringify(body)
@@ -588,7 +590,8 @@ function validateScopeInvariants(componentArea, targetPathsInput, writeGlobsInpu
588
590
  const scopeSeeds = collectScopeSeedPaths(targetPathsInput, writeGlobsInput);
589
591
  const normalizedComponentArea = normalizeAutonomyComponentArea(componentArea) ?? deriveAutonomyComponentArea(targetPathsInput, writeGlobsInput);
590
592
  const allowMultipleComponentRoots = options?.allowMultipleComponentRoots === true;
591
- if (!normalizedComponentArea && scopeSeeds.length > 1 && !allowMultipleComponentRoots) {
593
+ const hintsOnly = options?.hintsOnly === true;
594
+ if (!hintsOnly && !normalizedComponentArea && scopeSeeds.length > 1 && !allowMultipleComponentRoots) {
592
595
  errors.push(`scope spans multiple component roots: ${scopeSeeds.slice(0, 6).join(", ")}`);
593
596
  }
594
597
  const rootPrefix = normalizedComponentArea ? componentRootPrefix(normalizedComponentArea) : "";
@@ -600,7 +603,7 @@ function validateScopeInvariants(componentArea, targetPathsInput, writeGlobsInpu
600
603
  errors.push(`invalid target_path: ${String(raw ?? "")}`);
601
604
  continue;
602
605
  }
603
- if (rootPrefix && !underRoot(normalized, rootPrefix)) {
606
+ if (!hintsOnly && rootPrefix && !underRoot(normalized, rootPrefix)) {
604
607
  errors.push(`target_path outside component root: ${normalized}`);
605
608
  continue;
606
609
  }
@@ -621,20 +624,20 @@ function validateScopeInvariants(componentArea, targetPathsInput, writeGlobsInpu
621
624
  errors.push(`invalid write_glob: ${String(raw ?? "")}`);
622
625
  continue;
623
626
  }
624
- if (hasForbiddenBroadGlob(normalized)) {
627
+ if (!hintsOnly && hasForbiddenBroadGlob(normalized)) {
625
628
  errors.push(`forbidden broad write_glob: ${normalized}`);
626
629
  continue;
627
630
  }
628
631
  const prefix = literalPrefix(normalized);
629
- if (!prefix) {
632
+ if (!hintsOnly && !prefix) {
630
633
  errors.push(`write_glob literal prefix cannot be empty: ${normalized}`);
631
634
  continue;
632
635
  }
633
- if (rootPrefix && !underRoot(prefix, rootPrefix)) {
636
+ if (!hintsOnly && rootPrefix && !underRoot(prefix, rootPrefix)) {
634
637
  errors.push(`write_glob outside component root: ${normalized}`);
635
638
  continue;
636
639
  }
637
- if (!normalizedTargetPaths.some((targetPath) => targetPath === prefix || targetPath.startsWith(`${prefix}/`))) {
640
+ if (!hintsOnly && !normalizedTargetPaths.some((targetPath) => targetPath === prefix || targetPath.startsWith(`${prefix}/`))) {
638
641
  errors.push(`write_glob prefix does not align with target_paths: ${normalized}`);
639
642
  continue;
640
643
  }
@@ -647,14 +650,14 @@ function validateScopeInvariants(componentArea, targetPathsInput, writeGlobsInpu
647
650
  if ((options?.requireWriteGlobs ?? true) && normalizedWriteGlobs.length === 0) {
648
651
  errors.push("write_globs must be provided and non-empty");
649
652
  }
650
- if (normalizedTargetPaths.length > 0 && normalizedWriteGlobs.length > 0) {
653
+ if (!hintsOnly && normalizedTargetPaths.length > 0 && normalizedWriteGlobs.length > 0) {
651
654
  for (const targetPath of normalizedTargetPaths) {
652
655
  const covered = normalizedWriteGlobs.some((glob) => matchesGlob(targetPath, glob));
653
656
  if (!covered)
654
657
  errors.push(`target_path not covered by write_globs: ${targetPath}`);
655
658
  }
656
659
  }
657
- if (!normalizedComponentArea && !allowMultipleComponentRoots) {
660
+ if (!hintsOnly && !normalizedComponentArea && !allowMultipleComponentRoots) {
658
661
  errors.push("component_area could not be derived from scope");
659
662
  }
660
663
  const breadth = classifyGlobBreadth(normalizedWriteGlobs);
@@ -1357,7 +1360,7 @@ function loadPushPalsConfig(options = {}) {
1357
1360
  const parsed = Number.parseFloat(String(firstNonEmpty(process.env.REMOTEBUDDY_AUTONOMY_ALERT_AUTONOMY_FAILURE_RATE_THRESHOLD, asString(remoteAutonomyNode.alert_autonomy_failure_rate_threshold, "0.45"), "0.45")));
1358
1361
  return Number.isFinite(parsed) ? parsed : 0.45;
1359
1362
  })())),
1360
- allowReadAnywhere: parseBoolEnv("REMOTEBUDDY_AUTONOMY_ALLOW_READ_ANYWHERE") ?? asBoolean(remoteAutonomyNode.allow_read_anywhere, false),
1363
+ allowReadAnywhere: parseBoolEnv("REMOTEBUDDY_AUTONOMY_ALLOW_READ_ANYWHERE") ?? asBoolean(remoteAutonomyNode.allow_read_anywhere, true),
1361
1364
  prFeedbackCommentRows: Math.max(1, Math.min(200, asInt(parseIntEnv("REMOTEBUDDY_AUTONOMY_PR_FEEDBACK_COMMENT_ROWS") ?? remoteAutonomyNode.pr_feedback_comment_rows, 16))),
1362
1365
  prFeedbackCommentChars: Math.max(32, Math.min(20000, asInt(parseIntEnv("REMOTEBUDDY_AUTONOMY_PR_FEEDBACK_COMMENT_CHARS") ?? remoteAutonomyNode.pr_feedback_comment_chars, 600))),
1363
1366
  prFeedbackSummaryChars: Math.max(32, Math.min(20000, asInt(parseIntEnv("REMOTEBUDDY_AUTONOMY_PR_FEEDBACK_SUMMARY_CHARS") ?? remoteAutonomyNode.pr_feedback_summary_chars, 600))),
@@ -4257,15 +4260,13 @@ var POLICY = {
4257
4260
  }
4258
4261
  };
4259
4262
  var RISK_ORDER = { low: 0, medium: 1, high: 2 };
4260
- var BREADTH_ORDER = {
4261
- narrow: 0,
4262
- medium: 1,
4263
- broad: 2
4264
- };
4265
4263
  var IDEATION_SYSTEM_PROMPT = loadPromptTemplate("remotebuddy/autonomy_ideation_system_prompt.md").trim();
4266
4264
  var SCORING_SYSTEM_PROMPT = loadPromptTemplate("remotebuddy/autonomy_scoring_system_prompt.md").trim();
4267
4265
  var PLANNING_SYSTEM_PROMPT = loadPromptTemplate("remotebuddy/autonomy_planning_system_prompt.md").trim();
4268
4266
  var IDEATION_TIMEOUT_RECOVERY_INSTRUCTION = "Previous ideation timed out before you returned JSON. For this round only, stay within the time budget: prioritize the top 1-3 highest-confidence candidates, keep reasoning brief, avoid exhaustive exploration, and return valid JSON as soon as possible.";
4267
+ var STARTUP_FAST_TICK_MAX_ATTEMPTS = 4;
4268
+ var STARTUP_FAST_TICK_MAX_DELAY_MS = 15000;
4269
+ var STARTUP_STALE_LOCK_AFTER_MS = 30000;
4269
4270
  var VISION_DOC_FNAME = "vision.md";
4270
4271
  var MAX_VISION_SECTION_CHARS = 1200;
4271
4272
  var DOCS_MIN_IMPACT_SIGNAL_FOR_NO_PENALTY = 0.45;
@@ -4996,7 +4997,8 @@ function chooseRepoObjectiveTargetProfile(profiles, objective) {
4996
4997
  function adaptCandidateShapeToRepo(params) {
4997
4998
  const shape = params.shape;
4998
4999
  const scopeValidation = validateScopeInvariants(shape.component_area, shape.target_paths, shape.write_globs, {
4999
- requireWriteGlobs: true
5000
+ requireWriteGlobs: true,
5001
+ hintsOnly: true
5000
5002
  });
5001
5003
  const pathsExist = params.repoRoot && scopeValidation.ok ? findMissingRepoTargetPaths(params.repoRoot, scopeValidation.normalizedTargetPaths).length === 0 : scopeValidation.ok;
5002
5004
  if (scopeValidation.ok && pathsExist) {
@@ -5782,7 +5784,7 @@ ${pattern.tags.join(" ")}`.toLowerCase();
5782
5784
  const targetPaths = asStringArray2(metadataShape.target_paths ?? metadataShape.targetPaths ?? metadata.target_paths);
5783
5785
  const writeGlobs = asStringArray2(metadataShape.write_globs ?? metadataShape.writeGlobs ?? metadata.write_globs);
5784
5786
  const validationIdeas = asStringArray2(metadataShape.expected_validation ?? metadataShape.expectedValidation ?? metadata.expected_validation ?? pattern.validationIdeas);
5785
- const scopeCheck = validateScopeInvariants(componentArea, targetPaths.length > 0 ? targetPaths : defaults.target_paths, writeGlobs.length > 0 ? writeGlobs : defaults.write_globs, { requireWriteGlobs: true });
5787
+ const scopeCheck = validateScopeInvariants(componentArea, targetPaths.length > 0 ? targetPaths : defaults.target_paths, writeGlobs.length > 0 ? writeGlobs : defaults.write_globs, { requireWriteGlobs: true, hintsOnly: true });
5786
5788
  return adaptCandidateShapeToRepo({
5787
5789
  shape: {
5788
5790
  objective_type: objectiveType,
@@ -6101,6 +6103,92 @@ function buildEngineInspirationContext(params) {
6101
6103
  commit_history_hints: commitHistoryHints
6102
6104
  };
6103
6105
  }
6106
+ function compactIdeationText(value, maxChars) {
6107
+ const text = asString2(value).trim();
6108
+ if (text.length <= maxChars)
6109
+ return text;
6110
+ return `${text.slice(0, Math.max(0, maxChars - 1)).trimEnd()}...`;
6111
+ }
6112
+ function compactIdeationTextList(values, maxItems, maxChars) {
6113
+ return values.slice(0, maxItems).map((value) => compactIdeationText(value, maxChars)).filter(Boolean);
6114
+ }
6115
+ function compactVisionContextForIdeationRetry(vision) {
6116
+ const compactKeyItems = Object.fromEntries(Object.entries(vision.key_items).map(([key, value]) => [
6117
+ key,
6118
+ Array.isArray(value) ? compactIdeationTextList(value, 6, 260) : compactIdeationText(value, 260)
6119
+ ]));
6120
+ return {
6121
+ one_sentence: compactIdeationText(vision.one_sentence, 360),
6122
+ sections: vision.sections.slice(0, 4).map((section) => ({
6123
+ number: section.number,
6124
+ title: compactIdeationText(section.title, 160),
6125
+ markdown: compactIdeationText(section.markdown, 500),
6126
+ truncated: section.truncated || section.markdown.length > 500
6127
+ })),
6128
+ key_items: compactKeyItems,
6129
+ section_numbers: vision.section_numbers.slice(0, 8),
6130
+ truncated: vision.truncated
6131
+ };
6132
+ }
6133
+ function compactEngineInspirationForIdeationRetry(context) {
6134
+ return {
6135
+ compiled_repo_objectives: context.compiled_repo_objectives.slice(0, 4).map((objective) => ({
6136
+ id: objective.id,
6137
+ title: objective.title,
6138
+ weight: objective.weight,
6139
+ section_ref: objective.section_ref,
6140
+ category: objective.category,
6141
+ success_criteria: compactIdeationTextList(objective.success_criteria, 3, 220),
6142
+ validation_expectations: compactIdeationTextList(objective.validation_expectations, 3, 220)
6143
+ })),
6144
+ compiled_objectives: context.compiled_objectives.slice(0, 4).map((objective) => ({
6145
+ id: objective.id,
6146
+ title: compactIdeationText(objective.title, 220),
6147
+ weight: objective.weight,
6148
+ evidence: compactIdeationTextList(objective.evidence, 3, 220)
6149
+ })),
6150
+ opportunity_gaps: context.opportunity_gaps.slice(0, 4).map((gap) => ({
6151
+ id: gap.id,
6152
+ label: compactIdeationText(gap.label, 220),
6153
+ score: gap.score,
6154
+ evidence: compactIdeationTextList(gap.evidence, 3, 220)
6155
+ })),
6156
+ building_blocks: context.building_blocks.slice(0, 6).map((block) => ({
6157
+ id: block.id,
6158
+ algorithm: block.algorithm,
6159
+ summary: compactIdeationText(block.summary, 260),
6160
+ hypothesis: compactIdeationText(block.hypothesis, 260),
6161
+ score: block.score,
6162
+ objective_ids: block.objective_ids.slice(0, 3),
6163
+ gap_ids: block.gap_ids.slice(0, 3),
6164
+ candidate_shape: {
6165
+ objective_type: block.candidate_shape.objective_type,
6166
+ trigger_type: block.candidate_shape.trigger_type,
6167
+ component_area: block.candidate_shape.component_area,
6168
+ target_paths: block.candidate_shape.target_paths.slice(0, 4),
6169
+ write_globs: block.candidate_shape.write_globs.slice(0, 4)
6170
+ }
6171
+ })),
6172
+ source_patterns: context.source_patterns.slice(0, 4).map((pattern) => ({
6173
+ id: pattern.id,
6174
+ algorithm: pattern.algorithm,
6175
+ summary: compactIdeationText(pattern.summary, 260),
6176
+ tags: compactIdeationTextList(pattern.tags, 5, 80),
6177
+ quality_score: pattern.quality_score,
6178
+ freshness_score: pattern.freshness_score,
6179
+ source_trust_score: pattern.source_trust_score
6180
+ })),
6181
+ commit_history_hints: context.commit_history_hints.slice(0, 4).map((hint) => ({
6182
+ motif_id: hint.motif_id,
6183
+ label: compactIdeationText(hint.label, 220),
6184
+ count: hint.count,
6185
+ signal: hint.signal,
6186
+ objective_ids: hint.objective_ids.slice(0, 3),
6187
+ gap_ids: hint.gap_ids.slice(0, 3),
6188
+ sample_subjects: compactIdeationTextList(hint.sample_subjects, 3, 180)
6189
+ }))
6190
+ };
6191
+ }
6104
6192
  function selectVisionSectionRefs(sectionRefs) {
6105
6193
  const preferred = ["6", "7", "8", "4", "3", "0", "5"];
6106
6194
  const normalized = sectionRefs.map((value) => asString2(value)).filter(Boolean);
@@ -6200,7 +6288,7 @@ function buildRepoVisionFallbackCandidates(params) {
6200
6288
  const target = chooseRepoObjectiveTargetProfile(params.repoTargets ?? [], objective);
6201
6289
  const targetPaths = target?.target_paths ?? [objective.section_ref ? `vision.md` : "README.md"];
6202
6290
  const writeGlobs = target?.write_globs ?? targetPaths;
6203
- const componentArea = target?.component_area ?? (normalizeAutonomyComponentArea(pathDirname(targetPaths[0]) || targetPaths[0]) ?? "docs");
6291
+ const componentArea = target?.component_area ?? normalizeAutonomyComponentArea(pathDirname(targetPaths[0]) || targetPaths[0]) ?? "docs";
6204
6292
  const triggerType = categoryTriggerType(objective.category, params.snapshotTopSignals);
6205
6293
  const signalIds = pickSignalIdsForTrigger(params.snapshotTopSignals, triggerType);
6206
6294
  const sectionRef = objective.section_ref || sectionRefs[0] || "";
@@ -6217,7 +6305,7 @@ function buildRepoVisionFallbackCandidates(params) {
6217
6305
  component_area: componentArea,
6218
6306
  target_paths: targetPaths,
6219
6307
  scope: {
6220
- read_anywhere: false,
6308
+ read_anywhere: true,
6221
6309
  write_globs: writeGlobs
6222
6310
  },
6223
6311
  risk_level: "low",
@@ -6271,7 +6359,7 @@ function buildEngineFallbackCandidates(params) {
6271
6359
  component_area: candidateShape.component_area,
6272
6360
  target_paths: candidateShape.target_paths,
6273
6361
  scope: {
6274
- read_anywhere: false,
6362
+ read_anywhere: true,
6275
6363
  write_globs: candidateShape.write_globs
6276
6364
  },
6277
6365
  risk_level: candidateShape.risk_level,
@@ -6410,9 +6498,11 @@ class RemoteBuddyAutonomousEngine {
6410
6498
  cfg;
6411
6499
  runtimeEnabled = true;
6412
6500
  timer = null;
6501
+ startupFastTickTimer = null;
6413
6502
  heartbeatTimer = null;
6414
6503
  inFlight = false;
6415
6504
  nextTickAtMs = 0;
6505
+ startupFastTickAttemptsRemaining = 0;
6416
6506
  currentRunId = null;
6417
6507
  currentPhase = "idle";
6418
6508
  currentPhaseStartedAtMs = 0;
@@ -6442,6 +6532,8 @@ class RemoteBuddyAutonomousEngine {
6442
6532
  this.runtimeEnabled = Boolean(enabled);
6443
6533
  if (!this.runtimeEnabled) {
6444
6534
  this.nextTickAtMs = 0;
6535
+ this.startupFastTickAttemptsRemaining = 0;
6536
+ this.clearStartupFastTickTimer();
6445
6537
  if (!this.currentRunId) {
6446
6538
  this.lastOutcome = "skipped";
6447
6539
  this.lastDetail = "disabled_by_runtime_config";
@@ -6495,6 +6587,38 @@ class RemoteBuddyAutonomousEngine {
6495
6587
  lockStaleAfterMs() {
6496
6588
  return Math.max(this.phaseTimeoutMs("ideation") + 30000, this.cfg.heartbeatLogMs * 2, 120000);
6497
6589
  }
6590
+ startupLockStaleAfterMs() {
6591
+ return Math.min(this.lockStaleAfterMs(), Math.max(5000, Math.min(STARTUP_STALE_LOCK_AFTER_MS, Math.floor(this.cfg.tickIntervalMs / 4))));
6592
+ }
6593
+ lockStaleAfterMsForAcquire() {
6594
+ return this.startupFastTickAttemptsRemaining > 0 ? this.startupLockStaleAfterMs() : this.lockStaleAfterMs();
6595
+ }
6596
+ startupFastTickDelayMs() {
6597
+ return Math.max(1000, Math.min(STARTUP_FAST_TICK_MAX_DELAY_MS, Math.floor(this.cfg.tickIntervalMs / 10)));
6598
+ }
6599
+ clearStartupFastTickTimer() {
6600
+ if (this.startupFastTickTimer) {
6601
+ clearTimeout(this.startupFastTickTimer);
6602
+ this.startupFastTickTimer = null;
6603
+ }
6604
+ }
6605
+ scheduleStartupFastTick(reason) {
6606
+ if (!this.runtimeEnabled || !this.timer || this.startupFastTickTimer)
6607
+ return;
6608
+ if (this.startupFastTickAttemptsRemaining <= 0)
6609
+ return;
6610
+ const delayMs = this.startupFastTickDelayMs();
6611
+ this.startupFastTickAttemptsRemaining -= 1;
6612
+ this.nextTickAtMs = Date.now() + delayMs;
6613
+ console.log(`[RemoteBuddyAutonomousEngine] startup fast tick scheduled in ${delayMs}ms after ${reason} (remaining=${this.startupFastTickAttemptsRemaining}).`);
6614
+ this.startupFastTickTimer = setTimeout(() => {
6615
+ this.startupFastTickTimer = null;
6616
+ if (!this.runtimeEnabled || !this.timer)
6617
+ return;
6618
+ this.nextTickAtMs = Date.now() + this.cfg.tickIntervalMs;
6619
+ this.tick();
6620
+ }, delayMs);
6621
+ }
6498
6622
  cycleBudgetMs() {
6499
6623
  const ideationTimeoutMs = this.phaseTimeoutMs("ideation");
6500
6624
  const scoringTimeoutMs = this.phaseTimeoutMs("scoring");
@@ -6807,7 +6931,7 @@ class RemoteBuddyAutonomousEngine {
6807
6931
  sessionId: this.sessionId,
6808
6932
  runId,
6809
6933
  ttlMs,
6810
- staleAfterMs: this.lockStaleAfterMs()
6934
+ staleAfterMs: this.lockStaleAfterMsForAcquire()
6811
6935
  })
6812
6936
  });
6813
6937
  if (res.ok)
@@ -7123,6 +7247,8 @@ ${JSON.stringify(input.messages ?? [])}`),
7123
7247
  outcomeDetail = lockResult.reason ? compactStatusDetail(`lock_not_acquired:${lockResult.reason}`) : "lock_not_acquired";
7124
7248
  return;
7125
7249
  }
7250
+ this.startupFastTickAttemptsRemaining = 0;
7251
+ this.clearStartupFastTickTimer();
7126
7252
  this.setPhase("prepare_worktree");
7127
7253
  const ready = await this.ensureAutonomyRepoReady(runId);
7128
7254
  if (!ready) {
@@ -7231,58 +7357,79 @@ ${JSON.stringify(input.messages ?? [])}`),
7231
7357
  return;
7232
7358
  }
7233
7359
  this.setPhase("ideation");
7234
- const ideationRecovery = this.consumeIdeationTimeoutRecovery();
7235
- if (ideationRecovery) {
7236
- console.warn(`[RemoteBuddyAutonomousEngine] tick ${runId}: applying one-shot ideation timeout recovery from ${ideationRecovery.previousRunId} after ${ideationRecovery.timeoutMs}ms timeout.`);
7237
- }
7238
- const ideationTopSignals = snapshot.top_signals.slice(0, ideationRecovery ? 10 : 16);
7239
- const ideationStateTraits = snapshot.state_traits.slice(0, ideationRecovery ? 14 : 24);
7240
- const ideationFeedbackPriors = snapshot.feedback_priors.slice(0, ideationRecovery ? 12 : 20);
7241
- const ideationEngineIdeaPriors = (snapshot.engine_idea_priors ?? []).slice(0, ideationRecovery ? 12 : 20);
7242
- const ideationOpenObjectives = snapshot.open_objectives.slice(0, ideationRecovery ? 12 : 20);
7243
- const ideationActiveCooldowns = snapshot.active_cooldowns.slice(0, ideationRecovery ? 12 : 20);
7244
- const ideationRepoTargets = repoTargets.slice(0, ideationRecovery ? 8 : repoTargets.length);
7245
- const ideationPhase = await this.llmPhase("ideation", runId, snapshot.snapshot_id, {
7246
- system: IDEATION_SYSTEM_PROMPT,
7247
- json: true,
7248
- maxTokens: ideationRecovery ? 1400 : 2800,
7249
- temperature: 0.2,
7250
- messages: [
7251
- ...ideationRecovery ? [
7360
+ const buildIdeationInput = (ideationRecovery2, compactRetry) => {
7361
+ const reduced = compactRetry || Boolean(ideationRecovery2);
7362
+ const ideationTopSignals = snapshot.top_signals.slice(0, reduced ? 5 : 16);
7363
+ const ideationStateTraits = snapshot.state_traits.slice(0, reduced ? 6 : 24);
7364
+ const ideationFeedbackPriors = snapshot.feedback_priors.slice(0, reduced ? 4 : 20);
7365
+ const ideationEngineIdeaPriors = (snapshot.engine_idea_priors ?? []).slice(0, reduced ? 4 : 20);
7366
+ const ideationOpenObjectives = snapshot.open_objectives.slice(0, reduced ? 4 : 20);
7367
+ const ideationActiveCooldowns = snapshot.active_cooldowns.slice(0, reduced ? 4 : 20);
7368
+ const ideationRepoTargets = repoTargets.slice(0, reduced ? 4 : repoTargets.length);
7369
+ return {
7370
+ system: IDEATION_SYSTEM_PROMPT,
7371
+ json: true,
7372
+ maxTokens: reduced ? 900 : 2800,
7373
+ temperature: 0.2,
7374
+ messages: [
7375
+ ...ideationRecovery2 ? [
7376
+ {
7377
+ role: "user",
7378
+ content: `${IDEATION_TIMEOUT_RECOVERY_INSTRUCTION} Previous timed-out run: ${ideationRecovery2.previousRunId}. Timeout budget for this round: ${this.phaseTimeoutMs("ideation")}ms.`
7379
+ }
7380
+ ] : [],
7252
7381
  {
7253
7382
  role: "user",
7254
- content: `${IDEATION_TIMEOUT_RECOVERY_INSTRUCTION} Previous timed-out run: ${ideationRecovery.previousRunId}. Timeout budget for this round: ${this.phaseTimeoutMs("ideation")}ms.`
7383
+ content: JSON.stringify({
7384
+ snapshot: {
7385
+ snapshot_id: snapshot.snapshot_id,
7386
+ top_signals: ideationTopSignals,
7387
+ state_traits: ideationStateTraits,
7388
+ feedback_priors: ideationFeedbackPriors,
7389
+ engine_idea_priors: ideationEngineIdeaPriors,
7390
+ open_objectives: ideationOpenObjectives,
7391
+ active_cooldowns: ideationActiveCooldowns
7392
+ },
7393
+ vision: reduced ? compactVisionContextForIdeationRetry(visionContext) : visionContext,
7394
+ repo_targets: ideationRepoTargets.map((target) => ({
7395
+ component_area: target.component_area,
7396
+ target_paths: target.target_paths,
7397
+ write_globs: target.write_globs,
7398
+ label: target.label,
7399
+ keywords: target.keywords.slice(0, reduced ? 4 : 8)
7400
+ })),
7401
+ engine_inspiration: reduced ? compactEngineInspirationForIdeationRetry(engineInspiration) : engineInspiration,
7402
+ limits: {
7403
+ ideation_max_candidates: reduced ? Math.max(1, Math.min(3, this.cfg.ideationMaxCandidates)) : this.cfg.ideationMaxCandidates,
7404
+ min_confidence: this.cfg.minConfidence
7405
+ }
7406
+ }, null, reduced ? 0 : 2)
7255
7407
  }
7256
- ] : [],
7257
- {
7258
- role: "user",
7259
- content: JSON.stringify({
7260
- snapshot: {
7261
- snapshot_id: snapshot.snapshot_id,
7262
- top_signals: ideationTopSignals,
7263
- state_traits: ideationStateTraits,
7264
- feedback_priors: ideationFeedbackPriors,
7265
- engine_idea_priors: ideationEngineIdeaPriors,
7266
- open_objectives: ideationOpenObjectives,
7267
- active_cooldowns: ideationActiveCooldowns
7268
- },
7269
- vision: visionContext,
7270
- repo_targets: ideationRepoTargets.map((target) => ({
7271
- component_area: target.component_area,
7272
- target_paths: target.target_paths,
7273
- write_globs: target.write_globs,
7274
- label: target.label,
7275
- keywords: target.keywords.slice(0, 8)
7276
- })),
7277
- engine_inspiration: engineInspiration,
7278
- limits: {
7279
- ideation_max_candidates: this.cfg.ideationMaxCandidates,
7280
- min_confidence: this.cfg.minConfidence
7281
- }
7282
- }, null, 2)
7283
- }
7284
- ]
7285
- });
7408
+ ]
7409
+ };
7410
+ };
7411
+ let ideationRecovery = this.consumeIdeationTimeoutRecovery();
7412
+ if (ideationRecovery) {
7413
+ console.warn(`[RemoteBuddyAutonomousEngine] tick ${runId}: applying one-shot ideation timeout recovery from ${ideationRecovery.previousRunId} after ${ideationRecovery.timeoutMs}ms timeout.`);
7414
+ }
7415
+ let ideationPhase;
7416
+ try {
7417
+ ideationPhase = await this.llmPhase("ideation", runId, snapshot.snapshot_id, buildIdeationInput(ideationRecovery, Boolean(ideationRecovery)));
7418
+ } catch (error) {
7419
+ if (error instanceof Error && error.message === "autonomy ideation phase timeout" && !ideationRecovery) {
7420
+ ideationRecovery = {
7421
+ previousRunId: runId,
7422
+ timedOutAt: new Date().toISOString(),
7423
+ timeoutMs: this.phaseTimeoutMs("ideation")
7424
+ };
7425
+ this.pendingIdeationTimeoutRecovery = null;
7426
+ console.warn(`[RemoteBuddyAutonomousEngine] tick ${runId}: ideation timed out; retrying once immediately with reduced context and budget-focused guidance.`);
7427
+ ideationPhase = await this.llmPhase("ideation", runId, snapshot.snapshot_id, buildIdeationInput(ideationRecovery, true));
7428
+ this.pendingIdeationTimeoutRecovery = null;
7429
+ } else {
7430
+ throw error;
7431
+ }
7432
+ }
7286
7433
  llmCalls.push(ideationPhase.llmCall);
7287
7434
  const ideationJson = ideationPhase.json;
7288
7435
  if (this.isSnapshotExpired(snapshot) || Date.now() > cycleDeadline) {
@@ -7367,15 +7514,11 @@ ${JSON.stringify(input.messages ?? [])}`),
7367
7514
  recordDropReason(`${source}_risk_exceeds_policy`);
7368
7515
  continue;
7369
7516
  }
7370
- const scopeValidation = validateScopeInvariants(candidate.component_area, candidate.target_paths, candidate.scope.write_globs, { requireWriteGlobs: true });
7517
+ const scopeValidation = validateScopeInvariants(candidate.component_area, candidate.target_paths, candidate.scope.write_globs, { requireWriteGlobs: true, hintsOnly: true });
7371
7518
  if (!scopeValidation.ok) {
7372
7519
  recordDropReason(`${source}_scope_validation_failed`);
7373
7520
  continue;
7374
7521
  }
7375
- if (BREADTH_ORDER[scopeValidation.breadth] > BREADTH_ORDER[policy.maxBreadth]) {
7376
- recordDropReason(`${source}_scope_breadth_exceeds_policy`);
7377
- continue;
7378
- }
7379
7522
  if (candidate.scope.read_anywhere && !this.cfg.allowReadAnywhere) {
7380
7523
  recordDropReason(`${source}_read_anywhere_not_allowed`);
7381
7524
  continue;
@@ -7912,6 +8055,9 @@ Scope:
7912
8055
  await this.releaseDispatchLock(runId);
7913
8056
  this.inFlight = false;
7914
8057
  this.markTickDone(outcome, outcomeDetail);
8058
+ if (!lockAcquired && outcomeDetail.startsWith("lock_not_acquired")) {
8059
+ this.scheduleStartupFastTick("dispatch lock contention");
8060
+ }
7915
8061
  }
7916
8062
  }
7917
8063
  async enqueueFromAnalysis(instruction, autonomyCtx, originRequestId) {
@@ -7936,7 +8082,8 @@ Scope:
7936
8082
  if (!this.runtimeEnabled || this.timer)
7937
8083
  return;
7938
8084
  console.log(`[RemoteBuddyAutonomousEngine] Using dedicated autonomy worktree ${this.autonomyRepo} (remote=${this.gitRemote} integration=${this.integrationBranch} base=${this.baseBranch}).`);
7939
- this.nextTickAtMs = Date.now() + this.cfg.tickIntervalMs;
8085
+ this.startupFastTickAttemptsRemaining = STARTUP_FAST_TICK_MAX_ATTEMPTS;
8086
+ this.nextTickAtMs = Date.now();
7940
8087
  this.timer = setInterval(() => {
7941
8088
  this.nextTickAtMs = Date.now() + this.cfg.tickIntervalMs;
7942
8089
  this.tick();
@@ -7948,6 +8095,7 @@ Scope:
7948
8095
  this.tick();
7949
8096
  }
7950
8097
  stop() {
8098
+ this.clearStartupFastTickTimer();
7951
8099
  if (this.timer) {
7952
8100
  clearInterval(this.timer);
7953
8101
  this.timer = null;
@@ -7956,6 +8104,7 @@ Scope:
7956
8104
  clearInterval(this.heartbeatTimer);
7957
8105
  this.heartbeatTimer = null;
7958
8106
  }
8107
+ this.startupFastTickAttemptsRemaining = 0;
7959
8108
  this.nextTickAtMs = 0;
7960
8109
  }
7961
8110
  }
@@ -8266,12 +8415,13 @@ function buildExecutionGuidance(plan, targetPaths, requiredValidationSteps = [])
8266
8415
  const lines = [];
8267
8416
  const targets = normalizePathHints(targetPaths.length > 0 ? targetPaths : plan.scope.write_globs ?? []);
8268
8417
  if (targets.length > 0) {
8269
- lines.push("Target paths:");
8418
+ lines.push("Target paths / starting points:");
8270
8419
  for (const path of targets)
8271
8420
  lines.push(`- ${path}`);
8272
8421
  lines.push("Path handling:");
8273
8422
  lines.push("- Treat all target paths as repo-relative to the current working directory.");
8274
8423
  lines.push("- Do not prepend a leading slash to target paths.");
8424
+ lines.push("- These paths are relevance hints, not hard write boundaries; edit the behavior-owning files needed for the task and explain any expansion.");
8275
8425
  }
8276
8426
  lines.push("Scope:");
8277
8427
  lines.push(`- read_anywhere: ${plan.scope.read_anywhere ? "true" : "false"}`);
@@ -8280,12 +8430,12 @@ function buildExecutionGuidance(plan, targetPaths, requiredValidationSteps = [])
8280
8430
  lines.push(`- max_files_to_edit: ${plan.scope.max_files_to_edit}`);
8281
8431
  }
8282
8432
  if (Array.isArray(plan.scope.write_globs) && plan.scope.write_globs.length > 0) {
8283
- lines.push("Write globs:");
8433
+ lines.push("Write intent hints:");
8284
8434
  for (const glob of plan.scope.write_globs)
8285
8435
  lines.push(`- ${glob}`);
8286
8436
  }
8287
8437
  if (Array.isArray(plan.scope.forbidden_globs) && plan.scope.forbidden_globs.length > 0) {
8288
- lines.push("Forbidden globs:");
8438
+ lines.push("Review guardrail hints:");
8289
8439
  for (const glob of plan.scope.forbidden_globs)
8290
8440
  lines.push(`- ${glob}`);
8291
8441
  }
@@ -8556,6 +8706,7 @@ class RemoteBuddyOrchestrator {
8556
8706
  server;
8557
8707
  sessionId;
8558
8708
  authToken;
8709
+ fetchImpl;
8559
8710
  repo;
8560
8711
  jobsDbPath;
8561
8712
  workerOnlineTtlMs;
@@ -8624,6 +8775,7 @@ class RemoteBuddyOrchestrator {
8624
8775
  this.server = opts.server;
8625
8776
  this.sessionId = opts.sessionId;
8626
8777
  this.authToken = opts.authToken;
8778
+ this.fetchImpl = opts.fetchImpl ?? fetch;
8627
8779
  this.brain = opts.brain;
8628
8780
  this.idempotency = opts.idempotency;
8629
8781
  this.persistentMemory = opts.persistentMemory;
@@ -8687,7 +8839,8 @@ class RemoteBuddyOrchestrator {
8687
8839
  serverUrl: this.server,
8688
8840
  sessionId: this.sessionId,
8689
8841
  authToken: this.authToken,
8690
- from: `agent:${this.agentId}`
8842
+ from: `agent:${this.agentId}`,
8843
+ fetchImpl: this.fetchImpl
8691
8844
  });
8692
8845
  this.autonomousEngine = new RemoteBuddyAutonomousEngine({
8693
8846
  server: this.server,
@@ -8749,7 +8902,7 @@ class RemoteBuddyOrchestrator {
8749
8902
  async ensureSessionWithRetry(sessionId = this.sessionId, maxRetries = 20, baseDelayMs = 500, maxDelayMs = 5000) {
8750
8903
  for (let attempt = 1;attempt <= maxRetries && !this.disposed; attempt++) {
8751
8904
  try {
8752
- const res = await fetch(`${this.server}/sessions`, {
8905
+ const res = await this.fetchImpl(`${this.server}/sessions`, {
8753
8906
  method: "POST",
8754
8907
  headers: this.authHeaders(),
8755
8908
  body: JSON.stringify({ sessionId })
@@ -8796,7 +8949,7 @@ class RemoteBuddyOrchestrator {
8796
8949
  }
8797
8950
  async fetchJobLogs(jobId, limit = 80) {
8798
8951
  try {
8799
- const res = await fetch(`${this.server}/jobs/${jobId}/logs?limit=${Math.max(1, Math.min(500, limit))}`, {
8952
+ const res = await this.fetchImpl(`${this.server}/jobs/${jobId}/logs?limit=${Math.max(1, Math.min(500, limit))}`, {
8800
8953
  method: "GET",
8801
8954
  headers: this.authHeaders()
8802
8955
  });
@@ -8812,7 +8965,7 @@ class RemoteBuddyOrchestrator {
8812
8965
  }
8813
8966
  async fetchJobToolRuns(jobId, limit = 20) {
8814
8967
  try {
8815
- const res = await fetch(`${this.server}/jobs/${jobId}/tool-runs?limit=${Math.max(1, Math.min(100, limit))}`, {
8968
+ const res = await this.fetchImpl(`${this.server}/jobs/${jobId}/tool-runs?limit=${Math.max(1, Math.min(100, limit))}`, {
8816
8969
  method: "GET",
8817
8970
  headers: this.authHeaders()
8818
8971
  });
@@ -8868,7 +9021,7 @@ class RemoteBuddyOrchestrator {
8868
9021
  query.set("feedbackLimit", "3");
8869
9022
  const suffix = query.toString();
8870
9023
  try {
8871
- const res = await fetch(`${this.server}/autonomy/insights${suffix ? `?${suffix}` : ""}`, {
9024
+ const res = await this.fetchImpl(`${this.server}/autonomy/insights${suffix ? `?${suffix}` : ""}`, {
8872
9025
  method: "GET",
8873
9026
  headers: this.authHeaders()
8874
9027
  });
@@ -9169,7 +9322,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9169
9322
  payload.dedupeKey = dedupeKey;
9170
9323
  if (targetWorkerId)
9171
9324
  payload.targetWorkerId = targetWorkerId;
9172
- const res = await fetch(`${this.server}/jobs/enqueue`, {
9325
+ const res = await this.fetchImpl(`${this.server}/jobs/enqueue`, {
9173
9326
  method: "POST",
9174
9327
  headers: this.authHeaders(),
9175
9328
  body: JSON.stringify(payload)
@@ -9439,7 +9592,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9439
9592
  }
9440
9593
  async fetchWorkers() {
9441
9594
  try {
9442
- const res = await fetch(`${this.server}/workers?ttlMs=${this.workerOnlineTtlMs}`, {
9595
+ const res = await this.fetchImpl(`${this.server}/workers?ttlMs=${this.workerOnlineTtlMs}`, {
9443
9596
  method: "GET",
9444
9597
  headers: this.authHeaders()
9445
9598
  });
@@ -9453,7 +9606,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9453
9606
  }
9454
9607
  async fetchWorkerAutoscaleSnapshot() {
9455
9608
  try {
9456
- const res = await fetch(`${this.server}/workers/autoscale?ttlMs=${this.workerOnlineTtlMs}`, {
9609
+ const res = await this.fetchImpl(`${this.server}/workers/autoscale?ttlMs=${this.workerOnlineTtlMs}`, {
9457
9610
  method: "GET",
9458
9611
  headers: this.authHeaders()
9459
9612
  });
@@ -9715,7 +9868,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9715
9868
  const prompt = String(request.prompt ?? "").trim();
9716
9869
  if (!prompt) {
9717
9870
  console.warn(`[RemoteBuddy] Request ${requestId} missing prompt; marking failed`);
9718
- await fetch(`${this.server}/requests/${requestId}/fail`, {
9871
+ await this.fetchImpl(`${this.server}/requests/${requestId}/fail`, {
9719
9872
  method: "POST",
9720
9873
  headers: this.authHeaders(),
9721
9874
  body: JSON.stringify({ message: "Request missing prompt" })
@@ -9749,7 +9902,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9749
9902
  plan.job_kind = "task.execute";
9750
9903
  plan.lane = "worker";
9751
9904
  }
9752
- plan.scope.read_anywhere = false;
9905
+ plan.scope.read_anywhere = true;
9753
9906
  plan.scope.write_allowed = true;
9754
9907
  plan.scope.write_globs = [...autonomyMetadata.writeGlobs];
9755
9908
  }
@@ -9774,7 +9927,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9774
9927
  if (scopeCoverage.addedGlobs.length > 0) {
9775
9928
  console.warn(`[RemoteBuddy] Planner write_globs did not cover target paths. Added scope globs: ${scopeCoverage.addedGlobs.join(", ")}`);
9776
9929
  }
9777
- if (forceWorker) {
9930
+ if (forceWorker && !autonomyMetadata) {
9778
9931
  const concreteTargetCount = targetPaths.filter((entry) => entry && entry !== ".").length;
9779
9932
  if (concreteTargetCount > 0) {
9780
9933
  const currentMax = Number.isFinite(Number(plan.scope.max_files_to_edit)) && Number(plan.scope.max_files_to_edit) > 0 ? Math.floor(Number(plan.scope.max_files_to_edit)) : 0;
@@ -9783,9 +9936,6 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9783
9936
  }
9784
9937
  }
9785
9938
  }
9786
- if (autonomyMetadata && (!plan.scope.write_globs || plan.scope.write_globs.length === 0)) {
9787
- throw new Error("Autonomy-origin request requires non-empty planning.scope.write_globs before task dispatch.");
9788
- }
9789
9939
  if (plan.acceptance_criteria.length === 0) {
9790
9940
  plan.acceptance_criteria = ["Produce a correct and helpful result for the user request."];
9791
9941
  }
@@ -9844,7 +9994,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9844
9994
  await this.assistantMessage(requestSessionId, "Should I have a WorkerPal implement this? Reply to confirm and I'll enqueue the work, or clarify what you'd like focused on.", { turnId, correlationId: requestId, from: eventFrom });
9845
9995
  }
9846
9996
  }
9847
- await fetch(`${this.server}/requests/${requestId}/complete`, {
9997
+ await this.fetchImpl(`${this.server}/requests/${requestId}/complete`, {
9848
9998
  method: "POST",
9849
9999
  headers: this.authHeaders(),
9850
10000
  body: JSON.stringify({
@@ -9875,7 +10025,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
9875
10025
  correlationId: requestId,
9876
10026
  from: eventFrom
9877
10027
  });
9878
- await fetch(`${this.server}/requests/${requestId}/fail`, {
10028
+ await this.fetchImpl(`${this.server}/requests/${requestId}/fail`, {
9879
10029
  method: "POST",
9880
10030
  headers: this.authHeaders(),
9881
10031
  body: JSON.stringify({
@@ -10010,7 +10160,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
10010
10160
  await this.assistantMessage(requestSessionId, "I could not queue this WorkerPal task. No task was started.", { turnId, correlationId: requestId, from: eventFrom });
10011
10161
  this.rememberPersistentMemory("job_enqueue_failed", `enqueue_failed lane=${lane} intent=${plan.intent}`, requestId, requestSessionId);
10012
10162
  }
10013
- await fetch(`${this.server}/requests/${requestId}/complete`, {
10163
+ await this.fetchImpl(`${this.server}/requests/${requestId}/complete`, {
10014
10164
  method: "POST",
10015
10165
  headers: this.authHeaders(),
10016
10166
  body: JSON.stringify({
@@ -10041,7 +10191,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
10041
10191
  correlationId: requestId,
10042
10192
  from: eventFrom
10043
10193
  });
10044
- await fetch(`${this.server}/requests/${requestId}/fail`, {
10194
+ await this.fetchImpl(`${this.server}/requests/${requestId}/fail`, {
10045
10195
  method: "POST",
10046
10196
  headers: this.authHeaders(),
10047
10197
  body: JSON.stringify({
@@ -10056,7 +10206,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
10056
10206
  while (!this.disposed) {
10057
10207
  try {
10058
10208
  await this.maybeAutoscaleWorkers();
10059
- const res = await fetch(`${this.server}/requests/claim`, {
10209
+ const res = await this.fetchImpl(`${this.server}/requests/claim`, {
10060
10210
  method: "POST",
10061
10211
  headers: this.authHeaders(),
10062
10212
  body: JSON.stringify({ agentId: this.agentId })