@pushpalsdev/cli 1.0.93 → 1.0.95

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.
@@ -11,6 +11,7 @@ import {
11
11
  mkdirSync,
12
12
  readdirSync,
13
13
  readFileSync as readFileSync4,
14
+ renameSync,
14
15
  rmSync as rmSync2,
15
16
  writeFileSync
16
17
  } from "fs";
@@ -1978,8 +1979,29 @@ async function runCommandWithEnv(command, cwd, env, timeoutMs) {
1978
1979
  };
1979
1980
  }
1980
1981
  }
1982
+ function appendGitConfigEnv(env, key, value) {
1983
+ const existingCount = Number.parseInt(String(env.GIT_CONFIG_COUNT ?? "0").trim(), 10);
1984
+ const count = Number.isFinite(existingCount) && existingCount >= 0 ? existingCount : 0;
1985
+ for (let index = 0;index < count; index++) {
1986
+ if (String(env[`GIT_CONFIG_KEY_${index}`] ?? "") === key)
1987
+ return env;
1988
+ }
1989
+ return {
1990
+ ...env,
1991
+ GIT_CONFIG_COUNT: String(count + 1),
1992
+ [`GIT_CONFIG_KEY_${count}`]: key,
1993
+ [`GIT_CONFIG_VALUE_${count}`]: value
1994
+ };
1995
+ }
1996
+ function withWindowsGitSchannelEnv(env, platform = process.platform) {
1997
+ if (platform !== "win32")
1998
+ return env;
1999
+ if (parseBooleanFlag(env.PUSHPALS_DISABLE_WINDOWS_GIT_SCHANNEL) === true)
2000
+ return env;
2001
+ return appendGitConfigEnv(env, "http.sslBackend", "schannel");
2002
+ }
1981
2003
  async function runGitWithEnv(args, cwd, env) {
1982
- return await runCommandWithEnv(["git", ...args], cwd, env);
2004
+ return await runCommandWithEnv(["git", ...args], cwd, withWindowsGitSchannelEnv(env));
1983
2005
  }
1984
2006
  async function runGit(args, cwd) {
1985
2007
  return await runGitWithEnv(args, cwd, {
@@ -2264,15 +2286,59 @@ function resolveRuntimePlatformKey() {
2264
2286
  throw new Error(`Unsupported platform for embedded runtime binaries: ${process.platform}/${process.arch}`);
2265
2287
  }
2266
2288
  async function fetchLatestReleaseTag() {
2267
- const response = await fetchWithTimeout(`${GITHUB_API_URL}/releases/latest`, { headers: GITHUB_HEADERS }, 20000);
2268
- if (!response.ok) {
2269
- throw new Error(`Failed to resolve latest release tag (HTTP ${response.status})`);
2289
+ try {
2290
+ const response = await fetchWithTimeout(`${GITHUB_API_URL}/releases/latest`, { headers: GITHUB_HEADERS }, 20000);
2291
+ if (!response.ok) {
2292
+ throw new Error(`Failed to resolve latest release tag (HTTP ${response.status})`);
2293
+ }
2294
+ const payload = await response.json();
2295
+ const tagName = String(payload.tag_name ?? "").trim();
2296
+ if (!tagName)
2297
+ throw new Error("Latest release payload did not include tag_name");
2298
+ return tagName;
2299
+ } catch (err) {
2300
+ const fallback = await fetchLatestReleaseTagWithGitFallback(err);
2301
+ if (fallback)
2302
+ return fallback;
2303
+ throw err;
2270
2304
  }
2271
- const payload = await response.json();
2272
- const tagName = String(payload.tag_name ?? "").trim();
2273
- if (!tagName)
2274
- throw new Error("Latest release payload did not include tag_name");
2275
- return tagName;
2305
+ }
2306
+ async function fetchLatestReleaseTagWithGitFallback(cause) {
2307
+ const message = String(cause instanceof Error ? cause.message : cause ?? "");
2308
+ if (process.platform !== "win32" || !/certificate|cert_|unable to verify|self[- ]signed|tls|ssl/i.test(message) && !/fetch failed/i.test(message)) {
2309
+ return null;
2310
+ }
2311
+ console.warn("[pushpals] Bun could not verify the GitHub API certificate; resolving latest release tag with Git instead.");
2312
+ const result = await runGitWithEnv([
2313
+ "ls-remote",
2314
+ "--tags",
2315
+ "--refs",
2316
+ `https://github.com/${GITHUB_OWNER}/${GITHUB_REPO}.git`,
2317
+ "refs/tags/v*"
2318
+ ], process.cwd(), {
2319
+ ...process.env,
2320
+ GIT_TERMINAL_PROMPT: "0",
2321
+ GCM_INTERACTIVE: "Never"
2322
+ });
2323
+ if (!result.ok)
2324
+ return null;
2325
+ const tags = result.stdout.split(/\r?\n/g).map((line) => line.trim().match(/refs\/tags\/(v\d+\.\d+\.\d+(?:[-.][0-9A-Za-z.-]+)?)$/)?.[1]).filter((tag) => Boolean(tag));
2326
+ return sortReleaseTagsDescending(tags)[0] ?? null;
2327
+ }
2328
+ function sortReleaseTagsDescending(tags) {
2329
+ return [...new Set(tags)].sort((a, b) => compareReleaseTags(b, a));
2330
+ }
2331
+ function compareReleaseTags(a, b) {
2332
+ const parse = (value) => String(value).replace(/^v/i, "").split(/[.-]/g).map((part) => Number.parseInt(part, 10)).map((part) => Number.isFinite(part) ? part : 0);
2333
+ const left = parse(a);
2334
+ const right = parse(b);
2335
+ const max = Math.max(left.length, right.length, 3);
2336
+ for (let index = 0;index < max; index++) {
2337
+ const delta = (left[index] ?? 0) - (right[index] ?? 0);
2338
+ if (delta !== 0)
2339
+ return delta;
2340
+ }
2341
+ return a.localeCompare(b);
2276
2342
  }
2277
2343
  function resolvePreferredRuntimeReleaseTag(explicitTag, env = process.env) {
2278
2344
  const fromArg = String(explicitTag ?? "").trim();
@@ -2588,7 +2654,7 @@ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
2588
2654
  const existing = env[key];
2589
2655
  return typeof existing !== "string" || existing.trim().length === 0;
2590
2656
  })) : {};
2591
- return {
2657
+ const runtimeEnv = {
2592
2658
  ...inherited,
2593
2659
  PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
2594
2660
  PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
@@ -2609,6 +2675,7 @@ function buildEmbeddedRuntimeEnv(baseEnv, opts) {
2609
2675
  ...typeof env.PUSHPALS_DOCKER_BIN === "string" && env.PUSHPALS_DOCKER_BIN.trim() ? { PUSHPALS_DOCKER_BIN: env.PUSHPALS_DOCKER_BIN.trim() } : {},
2610
2676
  ...typeof env.PUSHPALS_DOCKER_BIN_ABSOLUTE === "string" && env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() ? { PUSHPALS_DOCKER_BIN_ABSOLUTE: env.PUSHPALS_DOCKER_BIN_ABSOLUTE.trim() } : {}
2611
2677
  };
2678
+ return withWindowsGitSchannelEnv(runtimeEnv, platform);
2612
2679
  }
2613
2680
  function parseBooleanFlag(raw) {
2614
2681
  const normalized = String(raw ?? "").trim().toLowerCase();
@@ -2752,13 +2819,51 @@ function readRemoteBuddyAutonomousEngineState(logPath) {
2752
2819
  async function downloadBinaryAsset(tag, assetName, outPath) {
2753
2820
  console.log(`[pushpals] Downloading embedded runtime binary ${assetName} from ${tag}...`);
2754
2821
  const url = `${GITHUB_RELEASE_URL}/${encodeURIComponent(tag)}/${assetName}`;
2755
- const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, 60000);
2756
- if (!response.ok) {
2757
- throw new Error(`Failed to download ${assetName} from ${tag} (HTTP ${response.status})`);
2822
+ try {
2823
+ const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, 60000);
2824
+ if (!response.ok) {
2825
+ throw new Error(`Failed to download ${assetName} from ${tag} (HTTP ${response.status})`);
2826
+ }
2827
+ const bytes = new Uint8Array(await response.arrayBuffer());
2828
+ mkdirSync(dirname(outPath), { recursive: true });
2829
+ await Bun.write(outPath, bytes);
2830
+ return;
2831
+ } catch (err) {
2832
+ const fallback = await downloadBinaryAssetWithWindowsCurlFallback(url, outPath, err);
2833
+ if (fallback)
2834
+ return;
2835
+ throw err;
2836
+ }
2837
+ }
2838
+ async function downloadBinaryAssetWithWindowsCurlFallback(url, outPath, cause) {
2839
+ if (process.platform !== "win32")
2840
+ return false;
2841
+ const message = String(cause instanceof Error ? cause.message : cause ?? "");
2842
+ if (!/certificate|cert_|unable to verify|self[- ]signed|tls|ssl/i.test(message) && !/fetch failed/i.test(message)) {
2843
+ return false;
2758
2844
  }
2759
- const bytes = new Uint8Array(await response.arrayBuffer());
2845
+ const tmpPath = `${outPath}.download-${process.pid}-${Date.now()}.tmp`;
2760
2846
  mkdirSync(dirname(outPath), { recursive: true });
2761
- await Bun.write(outPath, bytes);
2847
+ rmSync2(tmpPath, { force: true });
2848
+ console.warn("[pushpals] Bun could not verify the GitHub release certificate; retrying download with Windows curl certificate handling.");
2849
+ const result = await runCommandWithEnv([
2850
+ "curl.exe",
2851
+ "--fail",
2852
+ "--location",
2853
+ "--silent",
2854
+ "--show-error",
2855
+ "--ssl-no-revoke",
2856
+ "--output",
2857
+ tmpPath,
2858
+ url
2859
+ ], process.cwd(), process.env, 120000);
2860
+ if (!result.ok || !existsSync5(tmpPath)) {
2861
+ rmSync2(tmpPath, { force: true });
2862
+ const detail = result.stderr || result.stdout || `exit ${result.exitCode}`;
2863
+ throw new Error(`Windows curl fallback failed while downloading runtime binary: ${detail}`);
2864
+ }
2865
+ renameSync(tmpPath, outPath);
2866
+ return true;
2762
2867
  }
2763
2868
  async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
2764
2869
  const platformKey = resolveRuntimePlatformKey();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@pushpalsdev/cli",
3
- "version": "1.0.93",
3
+ "version": "1.0.95",
4
4
  "description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -45,8 +45,8 @@ Execution policy:
45
45
  - `scope.read_anywhere` should default to `true` (do not set `false` unless user explicitly requested restrictive reading)
46
46
  - `scope.write_allowed` should default to `true`
47
47
  - `scope.write_globs` should be included as starting-point/relevance hints, not as hard write boundaries
48
- - `scope.forbidden_globs` should be included only when specific paths must be blocked
49
- - `scope.max_files_to_edit` should be included only when a cap is needed
48
+ - `scope.forbidden_globs` should be included only as review guardrail hints, not as hard write blockers
49
+ - `scope.max_files_to_edit` should be included only as a planning/review hint; WorkerPal write access is repo-sandbox-wide
50
50
 
51
51
  Quality gates:
52
52
 
@@ -4264,6 +4264,9 @@ var IDEATION_SYSTEM_PROMPT = loadPromptTemplate("remotebuddy/autonomy_ideation_s
4264
4264
  var SCORING_SYSTEM_PROMPT = loadPromptTemplate("remotebuddy/autonomy_scoring_system_prompt.md").trim();
4265
4265
  var PLANNING_SYSTEM_PROMPT = loadPromptTemplate("remotebuddy/autonomy_planning_system_prompt.md").trim();
4266
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;
4267
4270
  var VISION_DOC_FNAME = "vision.md";
4268
4271
  var MAX_VISION_SECTION_CHARS = 1200;
4269
4272
  var DOCS_MIN_IMPACT_SIGNAL_FOR_NO_PENALTY = 0.45;
@@ -6100,6 +6103,92 @@ function buildEngineInspirationContext(params) {
6100
6103
  commit_history_hints: commitHistoryHints
6101
6104
  };
6102
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
+ }
6103
6192
  function selectVisionSectionRefs(sectionRefs) {
6104
6193
  const preferred = ["6", "7", "8", "4", "3", "0", "5"];
6105
6194
  const normalized = sectionRefs.map((value) => asString2(value)).filter(Boolean);
@@ -6199,7 +6288,7 @@ function buildRepoVisionFallbackCandidates(params) {
6199
6288
  const target = chooseRepoObjectiveTargetProfile(params.repoTargets ?? [], objective);
6200
6289
  const targetPaths = target?.target_paths ?? [objective.section_ref ? `vision.md` : "README.md"];
6201
6290
  const writeGlobs = target?.write_globs ?? targetPaths;
6202
- 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";
6203
6292
  const triggerType = categoryTriggerType(objective.category, params.snapshotTopSignals);
6204
6293
  const signalIds = pickSignalIdsForTrigger(params.snapshotTopSignals, triggerType);
6205
6294
  const sectionRef = objective.section_ref || sectionRefs[0] || "";
@@ -6409,9 +6498,11 @@ class RemoteBuddyAutonomousEngine {
6409
6498
  cfg;
6410
6499
  runtimeEnabled = true;
6411
6500
  timer = null;
6501
+ startupFastTickTimer = null;
6412
6502
  heartbeatTimer = null;
6413
6503
  inFlight = false;
6414
6504
  nextTickAtMs = 0;
6505
+ startupFastTickAttemptsRemaining = 0;
6415
6506
  currentRunId = null;
6416
6507
  currentPhase = "idle";
6417
6508
  currentPhaseStartedAtMs = 0;
@@ -6441,6 +6532,8 @@ class RemoteBuddyAutonomousEngine {
6441
6532
  this.runtimeEnabled = Boolean(enabled);
6442
6533
  if (!this.runtimeEnabled) {
6443
6534
  this.nextTickAtMs = 0;
6535
+ this.startupFastTickAttemptsRemaining = 0;
6536
+ this.clearStartupFastTickTimer();
6444
6537
  if (!this.currentRunId) {
6445
6538
  this.lastOutcome = "skipped";
6446
6539
  this.lastDetail = "disabled_by_runtime_config";
@@ -6494,6 +6587,38 @@ class RemoteBuddyAutonomousEngine {
6494
6587
  lockStaleAfterMs() {
6495
6588
  return Math.max(this.phaseTimeoutMs("ideation") + 30000, this.cfg.heartbeatLogMs * 2, 120000);
6496
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
+ }
6497
6622
  cycleBudgetMs() {
6498
6623
  const ideationTimeoutMs = this.phaseTimeoutMs("ideation");
6499
6624
  const scoringTimeoutMs = this.phaseTimeoutMs("scoring");
@@ -6806,7 +6931,7 @@ class RemoteBuddyAutonomousEngine {
6806
6931
  sessionId: this.sessionId,
6807
6932
  runId,
6808
6933
  ttlMs,
6809
- staleAfterMs: this.lockStaleAfterMs()
6934
+ staleAfterMs: this.lockStaleAfterMsForAcquire()
6810
6935
  })
6811
6936
  });
6812
6937
  if (res.ok)
@@ -7122,6 +7247,8 @@ ${JSON.stringify(input.messages ?? [])}`),
7122
7247
  outcomeDetail = lockResult.reason ? compactStatusDetail(`lock_not_acquired:${lockResult.reason}`) : "lock_not_acquired";
7123
7248
  return;
7124
7249
  }
7250
+ this.startupFastTickAttemptsRemaining = 0;
7251
+ this.clearStartupFastTickTimer();
7125
7252
  this.setPhase("prepare_worktree");
7126
7253
  const ready = await this.ensureAutonomyRepoReady(runId);
7127
7254
  if (!ready) {
@@ -7230,58 +7357,79 @@ ${JSON.stringify(input.messages ?? [])}`),
7230
7357
  return;
7231
7358
  }
7232
7359
  this.setPhase("ideation");
7233
- const ideationRecovery = this.consumeIdeationTimeoutRecovery();
7234
- if (ideationRecovery) {
7235
- console.warn(`[RemoteBuddyAutonomousEngine] tick ${runId}: applying one-shot ideation timeout recovery from ${ideationRecovery.previousRunId} after ${ideationRecovery.timeoutMs}ms timeout.`);
7236
- }
7237
- const ideationTopSignals = snapshot.top_signals.slice(0, ideationRecovery ? 10 : 16);
7238
- const ideationStateTraits = snapshot.state_traits.slice(0, ideationRecovery ? 14 : 24);
7239
- const ideationFeedbackPriors = snapshot.feedback_priors.slice(0, ideationRecovery ? 12 : 20);
7240
- const ideationEngineIdeaPriors = (snapshot.engine_idea_priors ?? []).slice(0, ideationRecovery ? 12 : 20);
7241
- const ideationOpenObjectives = snapshot.open_objectives.slice(0, ideationRecovery ? 12 : 20);
7242
- const ideationActiveCooldowns = snapshot.active_cooldowns.slice(0, ideationRecovery ? 12 : 20);
7243
- const ideationRepoTargets = repoTargets.slice(0, ideationRecovery ? 8 : repoTargets.length);
7244
- const ideationPhase = await this.llmPhase("ideation", runId, snapshot.snapshot_id, {
7245
- system: IDEATION_SYSTEM_PROMPT,
7246
- json: true,
7247
- maxTokens: ideationRecovery ? 1400 : 2800,
7248
- temperature: 0.2,
7249
- messages: [
7250
- ...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
+ ] : [],
7251
7381
  {
7252
7382
  role: "user",
7253
- 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)
7254
7407
  }
7255
- ] : [],
7256
- {
7257
- role: "user",
7258
- content: JSON.stringify({
7259
- snapshot: {
7260
- snapshot_id: snapshot.snapshot_id,
7261
- top_signals: ideationTopSignals,
7262
- state_traits: ideationStateTraits,
7263
- feedback_priors: ideationFeedbackPriors,
7264
- engine_idea_priors: ideationEngineIdeaPriors,
7265
- open_objectives: ideationOpenObjectives,
7266
- active_cooldowns: ideationActiveCooldowns
7267
- },
7268
- vision: visionContext,
7269
- repo_targets: ideationRepoTargets.map((target) => ({
7270
- component_area: target.component_area,
7271
- target_paths: target.target_paths,
7272
- write_globs: target.write_globs,
7273
- label: target.label,
7274
- keywords: target.keywords.slice(0, 8)
7275
- })),
7276
- engine_inspiration: engineInspiration,
7277
- limits: {
7278
- ideation_max_candidates: this.cfg.ideationMaxCandidates,
7279
- min_confidence: this.cfg.minConfidence
7280
- }
7281
- }, null, 2)
7282
- }
7283
- ]
7284
- });
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
+ }
7285
7433
  llmCalls.push(ideationPhase.llmCall);
7286
7434
  const ideationJson = ideationPhase.json;
7287
7435
  if (this.isSnapshotExpired(snapshot) || Date.now() > cycleDeadline) {
@@ -7907,6 +8055,9 @@ Scope:
7907
8055
  await this.releaseDispatchLock(runId);
7908
8056
  this.inFlight = false;
7909
8057
  this.markTickDone(outcome, outcomeDetail);
8058
+ if (!lockAcquired && outcomeDetail.startsWith("lock_not_acquired")) {
8059
+ this.scheduleStartupFastTick("dispatch lock contention");
8060
+ }
7910
8061
  }
7911
8062
  }
7912
8063
  async enqueueFromAnalysis(instruction, autonomyCtx, originRequestId) {
@@ -7931,7 +8082,8 @@ Scope:
7931
8082
  if (!this.runtimeEnabled || this.timer)
7932
8083
  return;
7933
8084
  console.log(`[RemoteBuddyAutonomousEngine] Using dedicated autonomy worktree ${this.autonomyRepo} (remote=${this.gitRemote} integration=${this.integrationBranch} base=${this.baseBranch}).`);
7934
- this.nextTickAtMs = Date.now() + this.cfg.tickIntervalMs;
8085
+ this.startupFastTickAttemptsRemaining = STARTUP_FAST_TICK_MAX_ATTEMPTS;
8086
+ this.nextTickAtMs = Date.now();
7935
8087
  this.timer = setInterval(() => {
7936
8088
  this.nextTickAtMs = Date.now() + this.cfg.tickIntervalMs;
7937
8089
  this.tick();
@@ -7943,6 +8095,7 @@ Scope:
7943
8095
  this.tick();
7944
8096
  }
7945
8097
  stop() {
8098
+ this.clearStartupFastTickTimer();
7946
8099
  if (this.timer) {
7947
8100
  clearInterval(this.timer);
7948
8101
  this.timer = null;
@@ -7951,6 +8104,7 @@ Scope:
7951
8104
  clearInterval(this.heartbeatTimer);
7952
8105
  this.heartbeatTimer = null;
7953
8106
  }
8107
+ this.startupFastTickAttemptsRemaining = 0;
7954
8108
  this.nextTickAtMs = 0;
7955
8109
  }
7956
8110
  }
@@ -8281,7 +8435,7 @@ function buildExecutionGuidance(plan, targetPaths, requiredValidationSteps = [])
8281
8435
  lines.push(`- ${glob}`);
8282
8436
  }
8283
8437
  if (Array.isArray(plan.scope.forbidden_globs) && plan.scope.forbidden_globs.length > 0) {
8284
- lines.push("Forbidden globs:");
8438
+ lines.push("Review guardrail hints:");
8285
8439
  for (const glob of plan.scope.forbidden_globs)
8286
8440
  lines.push(`- ${glob}`);
8287
8441
  }