@pushpalsdev/cli 1.0.86 → 1.0.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/pushpals-cli.js +1 -1
- package/package.json +2 -2
- package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +2 -1
- package/runtime/prompts/remotebuddy/autonomy_planning_system_prompt.md +1 -1
- package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +2 -2
- package/runtime/prompts/workerpals/miniswe_completion_requirement.md +1 -1
- package/runtime/prompts/workerpals/miniswe_explicit_targets_block.md +1 -1
- package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +4 -1
- package/runtime/prompts/workerpals/openhands_minimal_system_prompt.j2 +3 -1
- package/runtime/prompts/workerpals/openhands_task_execute_system_prompt.md +2 -1
- package/runtime/prompts/workerpals/workerpals_system_prompt.md +2 -2
- package/runtime/sandbox/.pushpals-remotebuddy-fallback.js +41 -45
- package/runtime/sandbox/apps/workerpals/src/backends/miniswe/miniswe_executor.py +5 -34
- package/runtime/sandbox/apps/workerpals/src/backends/openhands/openhands_executor.py +3 -2
- package/runtime/sandbox/apps/workerpals/src/execute_job.ts +25 -50
- package/runtime/sandbox/apps/workerpals/src/workerpals_main.ts +70 -25
- package/runtime/sandbox/packages/shared/src/autonomy_policy.ts +14 -8
- package/runtime/sandbox/packages/shared/src/communication.ts +4 -1
- package/runtime/sandbox/packages/shared/src/config.ts +1 -1
- package/runtime/sandbox/prompts/workerpals/miniswe_completion_requirement.md +1 -1
- package/runtime/sandbox/prompts/workerpals/miniswe_explicit_targets_block.md +1 -1
- package/runtime/sandbox/prompts/workerpals/openai_codex_task_execute_system_prompt.md +4 -1
- package/runtime/sandbox/prompts/workerpals/openhands_minimal_system_prompt.j2 +3 -1
- package/runtime/sandbox/prompts/workerpals/openhands_task_execute_system_prompt.md +2 -1
- package/runtime/sandbox/prompts/workerpals/workerpals_system_prompt.md +2 -2
package/dist/pushpals-cli.js
CHANGED
|
@@ -1071,7 +1071,7 @@ function loadPushPalsConfig(options = {}) {
|
|
|
1071
1071
|
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")));
|
|
1072
1072
|
return Number.isFinite(parsed) ? parsed : 0.45;
|
|
1073
1073
|
})())),
|
|
1074
|
-
allowReadAnywhere: parseBoolEnv("REMOTEBUDDY_AUTONOMY_ALLOW_READ_ANYWHERE") ?? asBoolean(remoteAutonomyNode.allow_read_anywhere,
|
|
1074
|
+
allowReadAnywhere: parseBoolEnv("REMOTEBUDDY_AUTONOMY_ALLOW_READ_ANYWHERE") ?? asBoolean(remoteAutonomyNode.allow_read_anywhere, true),
|
|
1075
1075
|
prFeedbackCommentRows: Math.max(1, Math.min(200, asInt(parseIntEnv("REMOTEBUDDY_AUTONOMY_PR_FEEDBACK_COMMENT_ROWS") ?? remoteAutonomyNode.pr_feedback_comment_rows, 16))),
|
|
1076
1076
|
prFeedbackCommentChars: Math.max(32, Math.min(20000, asInt(parseIntEnv("REMOTEBUDDY_AUTONOMY_PR_FEEDBACK_COMMENT_CHARS") ?? remoteAutonomyNode.pr_feedback_comment_chars, 600))),
|
|
1077
1077
|
prFeedbackSummaryChars: Math.max(32, Math.min(20000, asInt(parseIntEnv("REMOTEBUDDY_AUTONOMY_PR_FEEDBACK_SUMMARY_CHARS") ?? remoteAutonomyNode.pr_feedback_summary_chars, 600))),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@pushpalsdev/cli",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.93",
|
|
4
4
|
"description": "PushPals terminal CLI for LocalBuddy -> RemoteBuddy orchestration",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"url": "https://github.com/PushPalsDev/pushpals/issues"
|
|
13
13
|
},
|
|
14
14
|
"bin": {
|
|
15
|
-
"pushpals": "
|
|
15
|
+
"pushpals": "bin/pushpals.cjs"
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"bin",
|
|
@@ -56,9 +56,10 @@ Constraints:
|
|
|
56
56
|
- `objective_type` is a governance lane, not a fixed feature catalog. Feature ideas are free-form and should be expressed in `title`, `problem_statement`, and `feature_hypotheses`.
|
|
57
57
|
- `feature_hypotheses` may contain any suitable product/engineering features; keep each item concise and actionable.
|
|
58
58
|
- target_paths must be literal repo-relative paths.
|
|
59
|
-
- write_globs must be repo-relative globs.
|
|
59
|
+
- write_globs must be repo-relative globs used as starting-point/relevance hints, not hard write boundaries.
|
|
60
60
|
- Choose target_paths that own the behavior being improved, not thin route wrappers, re-export files, or shell components, unless the requested change is explicitly at that wrapper boundary.
|
|
61
61
|
- For UI/game/product-surface objectives, prefer files that render or compute the relevant state directly; use wrapper files only for navigation, mounting, or screen-level chrome work.
|
|
62
|
+
- Workers have repo-wide sandbox write access and may expand from these hints to the behavior-owning files; the review agent will judge whether the final diff stays relevant.
|
|
62
63
|
- do not invent evidence ids.
|
|
63
64
|
- If all signals are low/noisy, it is valid to return zero candidates.
|
|
64
65
|
- Treat a low `sig_queue_health` value as maintenance-window evidence for safe proactive work, not only incident response.
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
Write objective instruction text for a worker.
|
|
2
2
|
Return strict JSON:
|
|
3
3
|
{ "instruction": "..." }
|
|
4
|
-
Keep it concise
|
|
4
|
+
Keep it concise and executable. Treat target_paths and write_globs as starting-point/relevance hints, not hard write boundaries; the worker may edit other behavior-owning repo files when needed and the review agent will judge relevance.
|
|
5
5
|
If you mention commands, use Bun/Bunx command forms (`bun ...`, `bunx ...`), never npm/npx/pnpm/yarn forms.
|
|
@@ -11,7 +11,7 @@ Repository boundary policy:
|
|
|
11
11
|
|
|
12
12
|
- Treat `{{repo_root}}` as the only allowed repository scope.
|
|
13
13
|
- Never plan edits or checks outside this repository root.
|
|
14
|
-
- Prefer explicit repo-relative targets
|
|
14
|
+
- Prefer explicit repo-relative targets as review/relevance hints. WorkerPals have repo-wide sandbox write access, so do not over-constrain write scope unless the user explicitly asks for a hard path limit.
|
|
15
15
|
|
|
16
16
|
Intent taxonomy (choose the single best fit):
|
|
17
17
|
|
|
@@ -44,7 +44,7 @@ Execution policy:
|
|
|
44
44
|
- Scope policy (for `requires_worker=true`):
|
|
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
|
-
- `scope.write_globs` should be included
|
|
47
|
+
- `scope.write_globs` should be included as starting-point/relevance hints, not as hard write boundaries
|
|
48
48
|
- `scope.forbidden_globs` should be included only when specific paths must be blocked
|
|
49
49
|
- `scope.max_files_to_edit` should be included only when a cap is needed
|
|
50
50
|
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Completion requirement:
|
|
1
|
+
Completion requirement: solve the requested task before setting done=true. Target paths are relevance hints; edit other behavior-owning files when needed and explain why.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
Target path hints:
|
|
2
2
|
{{targets_block}}
|
|
@@ -11,7 +11,10 @@ Non-negotiable runtime invariants:
|
|
|
11
11
|
|
|
12
12
|
Execution rules:
|
|
13
13
|
|
|
14
|
-
- Keep edits minimal, correct, and
|
|
14
|
+
- Keep edits minimal, correct, and relevant to the requested task.
|
|
15
|
+
- You have repo-wide read/write access inside an isolated WorkerPal sandbox. Target paths and write globs are starting-point/relevance hints, not hard write boundaries.
|
|
16
|
+
- If the hinted file is a thin wrapper or the behavior lives elsewhere, edit the behavior-owning file(s) needed to solve the task and explain the scope expansion in your final response.
|
|
17
|
+
- Avoid irrelevant sprawl; the review agent will judge whether changed files are necessary for the requested outcome.
|
|
15
18
|
- Read relevant files before editing, then run focused validation.
|
|
16
19
|
- Use direct commands without shell wrappers. Prefer plain commands like `git diff -- path`, `git add <path>`, `git status --porcelain`, and `pwd`.
|
|
17
20
|
- Do not wrap commands in `/bin/bash -lc`, `sh -lc`, `cmd /c`, or `powershell -Command`, and avoid pipelines, `awk`, heredocs, or multi-command shell snippets unless they are truly unavoidable.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
You are PushPals WorkerPal running inside OpenHands.
|
|
2
2
|
|
|
3
|
-
Operate with strict
|
|
3
|
+
Operate with strict relevance:
|
|
4
4
|
- Focus only on the current user task.
|
|
5
5
|
- If the task is a question, answer directly and do not edit files.
|
|
6
6
|
- If code/file changes are requested, implement them end-to-end.
|
|
7
7
|
- Reuse existing repository conventions and avoid unrelated refactors/docs.
|
|
8
|
+
- Treat target paths and write globs as starting-point/relevance hints, not hard write boundaries.
|
|
9
|
+
- If the behavior lives outside the hinted files, edit the owning files and explain the scope expansion.
|
|
8
10
|
|
|
9
11
|
Execution loop:
|
|
10
12
|
- Prefer concrete execution over planning chatter.
|
|
@@ -3,7 +3,8 @@ You are PushPals WorkerPal running inside OpenHands.
|
|
|
3
3
|
Execution rules:
|
|
4
4
|
|
|
5
5
|
- Focus only on the task below.
|
|
6
|
-
- Keep changes minimal, correct, and
|
|
6
|
+
- Keep changes minimal, correct, and relevant to the request.
|
|
7
|
+
- Target paths and write globs are starting-point/relevance hints, not hard write boundaries; if the behavior lives elsewhere, edit the owning files and explain why.
|
|
7
8
|
- Read relevant files before editing.
|
|
8
9
|
- Reuse existing project conventions and tooling.
|
|
9
10
|
- If the task is a question/explanation, answer directly and do not edit files.
|
|
@@ -6,7 +6,7 @@ Your mission:
|
|
|
6
6
|
|
|
7
7
|
- Take the user (or RemoteBuddy) request and fully execute it end-to-end.
|
|
8
8
|
- You are responsible for breaking the work down into concrete subtasks, completing them, validating, reviewing your own changes, and preparing a high-quality commit message when the work is ready.
|
|
9
|
-
- You have full read/write access within the assigned repository only; do not access files outside that repository.
|
|
9
|
+
- You have full read/write access within the assigned repository sandbox only; do not access files outside that repository. Target paths and write globs are starting-point/relevance hints, not hard write boundaries.
|
|
10
10
|
|
|
11
11
|
Mindset:
|
|
12
12
|
|
|
@@ -64,7 +64,7 @@ Execution workflow (you MUST follow this):
|
|
|
64
64
|
- Performance: no unnecessary work on UI thread, no extra network calls, no large bundles
|
|
65
65
|
- Cross-platform: iOS/Android/Web differences guarded appropriately
|
|
66
66
|
- Security: no secret leakage, safe networking defaults, no unsafe shell usage
|
|
67
|
-
- Make any final polish edits that improve clarity without
|
|
67
|
+
- Make any final polish edits that improve clarity without drifting from the task.
|
|
68
68
|
|
|
69
69
|
7. Prepare to commit (when appropriate)
|
|
70
70
|
- When the work is ready, produce a detailed commit message (do NOT actually commit unless your system explicitly allows it).
|
|
@@ -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
|
|
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
|
-
|
|
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,
|
|
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,11 +4260,6 @@ 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();
|
|
@@ -4996,7 +4994,8 @@ function chooseRepoObjectiveTargetProfile(profiles, objective) {
|
|
|
4996
4994
|
function adaptCandidateShapeToRepo(params) {
|
|
4997
4995
|
const shape = params.shape;
|
|
4998
4996
|
const scopeValidation = validateScopeInvariants(shape.component_area, shape.target_paths, shape.write_globs, {
|
|
4999
|
-
requireWriteGlobs: true
|
|
4997
|
+
requireWriteGlobs: true,
|
|
4998
|
+
hintsOnly: true
|
|
5000
4999
|
});
|
|
5001
5000
|
const pathsExist = params.repoRoot && scopeValidation.ok ? findMissingRepoTargetPaths(params.repoRoot, scopeValidation.normalizedTargetPaths).length === 0 : scopeValidation.ok;
|
|
5002
5001
|
if (scopeValidation.ok && pathsExist) {
|
|
@@ -5782,7 +5781,7 @@ ${pattern.tags.join(" ")}`.toLowerCase();
|
|
|
5782
5781
|
const targetPaths = asStringArray2(metadataShape.target_paths ?? metadataShape.targetPaths ?? metadata.target_paths);
|
|
5783
5782
|
const writeGlobs = asStringArray2(metadataShape.write_globs ?? metadataShape.writeGlobs ?? metadata.write_globs);
|
|
5784
5783
|
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 });
|
|
5784
|
+
const scopeCheck = validateScopeInvariants(componentArea, targetPaths.length > 0 ? targetPaths : defaults.target_paths, writeGlobs.length > 0 ? writeGlobs : defaults.write_globs, { requireWriteGlobs: true, hintsOnly: true });
|
|
5786
5785
|
return adaptCandidateShapeToRepo({
|
|
5787
5786
|
shape: {
|
|
5788
5787
|
objective_type: objectiveType,
|
|
@@ -6217,7 +6216,7 @@ function buildRepoVisionFallbackCandidates(params) {
|
|
|
6217
6216
|
component_area: componentArea,
|
|
6218
6217
|
target_paths: targetPaths,
|
|
6219
6218
|
scope: {
|
|
6220
|
-
read_anywhere:
|
|
6219
|
+
read_anywhere: true,
|
|
6221
6220
|
write_globs: writeGlobs
|
|
6222
6221
|
},
|
|
6223
6222
|
risk_level: "low",
|
|
@@ -6271,7 +6270,7 @@ function buildEngineFallbackCandidates(params) {
|
|
|
6271
6270
|
component_area: candidateShape.component_area,
|
|
6272
6271
|
target_paths: candidateShape.target_paths,
|
|
6273
6272
|
scope: {
|
|
6274
|
-
read_anywhere:
|
|
6273
|
+
read_anywhere: true,
|
|
6275
6274
|
write_globs: candidateShape.write_globs
|
|
6276
6275
|
},
|
|
6277
6276
|
risk_level: candidateShape.risk_level,
|
|
@@ -7367,15 +7366,11 @@ ${JSON.stringify(input.messages ?? [])}`),
|
|
|
7367
7366
|
recordDropReason(`${source}_risk_exceeds_policy`);
|
|
7368
7367
|
continue;
|
|
7369
7368
|
}
|
|
7370
|
-
const scopeValidation = validateScopeInvariants(candidate.component_area, candidate.target_paths, candidate.scope.write_globs, { requireWriteGlobs: true });
|
|
7369
|
+
const scopeValidation = validateScopeInvariants(candidate.component_area, candidate.target_paths, candidate.scope.write_globs, { requireWriteGlobs: true, hintsOnly: true });
|
|
7371
7370
|
if (!scopeValidation.ok) {
|
|
7372
7371
|
recordDropReason(`${source}_scope_validation_failed`);
|
|
7373
7372
|
continue;
|
|
7374
7373
|
}
|
|
7375
|
-
if (BREADTH_ORDER[scopeValidation.breadth] > BREADTH_ORDER[policy.maxBreadth]) {
|
|
7376
|
-
recordDropReason(`${source}_scope_breadth_exceeds_policy`);
|
|
7377
|
-
continue;
|
|
7378
|
-
}
|
|
7379
7374
|
if (candidate.scope.read_anywhere && !this.cfg.allowReadAnywhere) {
|
|
7380
7375
|
recordDropReason(`${source}_read_anywhere_not_allowed`);
|
|
7381
7376
|
continue;
|
|
@@ -8266,12 +8261,13 @@ function buildExecutionGuidance(plan, targetPaths, requiredValidationSteps = [])
|
|
|
8266
8261
|
const lines = [];
|
|
8267
8262
|
const targets = normalizePathHints(targetPaths.length > 0 ? targetPaths : plan.scope.write_globs ?? []);
|
|
8268
8263
|
if (targets.length > 0) {
|
|
8269
|
-
lines.push("Target paths:");
|
|
8264
|
+
lines.push("Target paths / starting points:");
|
|
8270
8265
|
for (const path of targets)
|
|
8271
8266
|
lines.push(`- ${path}`);
|
|
8272
8267
|
lines.push("Path handling:");
|
|
8273
8268
|
lines.push("- Treat all target paths as repo-relative to the current working directory.");
|
|
8274
8269
|
lines.push("- Do not prepend a leading slash to target paths.");
|
|
8270
|
+
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
8271
|
}
|
|
8276
8272
|
lines.push("Scope:");
|
|
8277
8273
|
lines.push(`- read_anywhere: ${plan.scope.read_anywhere ? "true" : "false"}`);
|
|
@@ -8280,7 +8276,7 @@ function buildExecutionGuidance(plan, targetPaths, requiredValidationSteps = [])
|
|
|
8280
8276
|
lines.push(`- max_files_to_edit: ${plan.scope.max_files_to_edit}`);
|
|
8281
8277
|
}
|
|
8282
8278
|
if (Array.isArray(plan.scope.write_globs) && plan.scope.write_globs.length > 0) {
|
|
8283
|
-
lines.push("Write
|
|
8279
|
+
lines.push("Write intent hints:");
|
|
8284
8280
|
for (const glob of plan.scope.write_globs)
|
|
8285
8281
|
lines.push(`- ${glob}`);
|
|
8286
8282
|
}
|
|
@@ -8556,6 +8552,7 @@ class RemoteBuddyOrchestrator {
|
|
|
8556
8552
|
server;
|
|
8557
8553
|
sessionId;
|
|
8558
8554
|
authToken;
|
|
8555
|
+
fetchImpl;
|
|
8559
8556
|
repo;
|
|
8560
8557
|
jobsDbPath;
|
|
8561
8558
|
workerOnlineTtlMs;
|
|
@@ -8624,6 +8621,7 @@ class RemoteBuddyOrchestrator {
|
|
|
8624
8621
|
this.server = opts.server;
|
|
8625
8622
|
this.sessionId = opts.sessionId;
|
|
8626
8623
|
this.authToken = opts.authToken;
|
|
8624
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
8627
8625
|
this.brain = opts.brain;
|
|
8628
8626
|
this.idempotency = opts.idempotency;
|
|
8629
8627
|
this.persistentMemory = opts.persistentMemory;
|
|
@@ -8687,7 +8685,8 @@ class RemoteBuddyOrchestrator {
|
|
|
8687
8685
|
serverUrl: this.server,
|
|
8688
8686
|
sessionId: this.sessionId,
|
|
8689
8687
|
authToken: this.authToken,
|
|
8690
|
-
from: `agent:${this.agentId}
|
|
8688
|
+
from: `agent:${this.agentId}`,
|
|
8689
|
+
fetchImpl: this.fetchImpl
|
|
8691
8690
|
});
|
|
8692
8691
|
this.autonomousEngine = new RemoteBuddyAutonomousEngine({
|
|
8693
8692
|
server: this.server,
|
|
@@ -8749,7 +8748,7 @@ class RemoteBuddyOrchestrator {
|
|
|
8749
8748
|
async ensureSessionWithRetry(sessionId = this.sessionId, maxRetries = 20, baseDelayMs = 500, maxDelayMs = 5000) {
|
|
8750
8749
|
for (let attempt = 1;attempt <= maxRetries && !this.disposed; attempt++) {
|
|
8751
8750
|
try {
|
|
8752
|
-
const res = await
|
|
8751
|
+
const res = await this.fetchImpl(`${this.server}/sessions`, {
|
|
8753
8752
|
method: "POST",
|
|
8754
8753
|
headers: this.authHeaders(),
|
|
8755
8754
|
body: JSON.stringify({ sessionId })
|
|
@@ -8796,7 +8795,7 @@ class RemoteBuddyOrchestrator {
|
|
|
8796
8795
|
}
|
|
8797
8796
|
async fetchJobLogs(jobId, limit = 80) {
|
|
8798
8797
|
try {
|
|
8799
|
-
const res = await
|
|
8798
|
+
const res = await this.fetchImpl(`${this.server}/jobs/${jobId}/logs?limit=${Math.max(1, Math.min(500, limit))}`, {
|
|
8800
8799
|
method: "GET",
|
|
8801
8800
|
headers: this.authHeaders()
|
|
8802
8801
|
});
|
|
@@ -8812,7 +8811,7 @@ class RemoteBuddyOrchestrator {
|
|
|
8812
8811
|
}
|
|
8813
8812
|
async fetchJobToolRuns(jobId, limit = 20) {
|
|
8814
8813
|
try {
|
|
8815
|
-
const res = await
|
|
8814
|
+
const res = await this.fetchImpl(`${this.server}/jobs/${jobId}/tool-runs?limit=${Math.max(1, Math.min(100, limit))}`, {
|
|
8816
8815
|
method: "GET",
|
|
8817
8816
|
headers: this.authHeaders()
|
|
8818
8817
|
});
|
|
@@ -8868,7 +8867,7 @@ class RemoteBuddyOrchestrator {
|
|
|
8868
8867
|
query.set("feedbackLimit", "3");
|
|
8869
8868
|
const suffix = query.toString();
|
|
8870
8869
|
try {
|
|
8871
|
-
const res = await
|
|
8870
|
+
const res = await this.fetchImpl(`${this.server}/autonomy/insights${suffix ? `?${suffix}` : ""}`, {
|
|
8872
8871
|
method: "GET",
|
|
8873
8872
|
headers: this.authHeaders()
|
|
8874
8873
|
});
|
|
@@ -9169,7 +9168,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9169
9168
|
payload.dedupeKey = dedupeKey;
|
|
9170
9169
|
if (targetWorkerId)
|
|
9171
9170
|
payload.targetWorkerId = targetWorkerId;
|
|
9172
|
-
const res = await
|
|
9171
|
+
const res = await this.fetchImpl(`${this.server}/jobs/enqueue`, {
|
|
9173
9172
|
method: "POST",
|
|
9174
9173
|
headers: this.authHeaders(),
|
|
9175
9174
|
body: JSON.stringify(payload)
|
|
@@ -9439,7 +9438,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9439
9438
|
}
|
|
9440
9439
|
async fetchWorkers() {
|
|
9441
9440
|
try {
|
|
9442
|
-
const res = await
|
|
9441
|
+
const res = await this.fetchImpl(`${this.server}/workers?ttlMs=${this.workerOnlineTtlMs}`, {
|
|
9443
9442
|
method: "GET",
|
|
9444
9443
|
headers: this.authHeaders()
|
|
9445
9444
|
});
|
|
@@ -9453,7 +9452,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9453
9452
|
}
|
|
9454
9453
|
async fetchWorkerAutoscaleSnapshot() {
|
|
9455
9454
|
try {
|
|
9456
|
-
const res = await
|
|
9455
|
+
const res = await this.fetchImpl(`${this.server}/workers/autoscale?ttlMs=${this.workerOnlineTtlMs}`, {
|
|
9457
9456
|
method: "GET",
|
|
9458
9457
|
headers: this.authHeaders()
|
|
9459
9458
|
});
|
|
@@ -9715,7 +9714,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9715
9714
|
const prompt = String(request.prompt ?? "").trim();
|
|
9716
9715
|
if (!prompt) {
|
|
9717
9716
|
console.warn(`[RemoteBuddy] Request ${requestId} missing prompt; marking failed`);
|
|
9718
|
-
await
|
|
9717
|
+
await this.fetchImpl(`${this.server}/requests/${requestId}/fail`, {
|
|
9719
9718
|
method: "POST",
|
|
9720
9719
|
headers: this.authHeaders(),
|
|
9721
9720
|
body: JSON.stringify({ message: "Request missing prompt" })
|
|
@@ -9749,7 +9748,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9749
9748
|
plan.job_kind = "task.execute";
|
|
9750
9749
|
plan.lane = "worker";
|
|
9751
9750
|
}
|
|
9752
|
-
plan.scope.read_anywhere =
|
|
9751
|
+
plan.scope.read_anywhere = true;
|
|
9753
9752
|
plan.scope.write_allowed = true;
|
|
9754
9753
|
plan.scope.write_globs = [...autonomyMetadata.writeGlobs];
|
|
9755
9754
|
}
|
|
@@ -9774,7 +9773,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9774
9773
|
if (scopeCoverage.addedGlobs.length > 0) {
|
|
9775
9774
|
console.warn(`[RemoteBuddy] Planner write_globs did not cover target paths. Added scope globs: ${scopeCoverage.addedGlobs.join(", ")}`);
|
|
9776
9775
|
}
|
|
9777
|
-
if (forceWorker) {
|
|
9776
|
+
if (forceWorker && !autonomyMetadata) {
|
|
9778
9777
|
const concreteTargetCount = targetPaths.filter((entry) => entry && entry !== ".").length;
|
|
9779
9778
|
if (concreteTargetCount > 0) {
|
|
9780
9779
|
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 +9782,6 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9783
9782
|
}
|
|
9784
9783
|
}
|
|
9785
9784
|
}
|
|
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
9785
|
if (plan.acceptance_criteria.length === 0) {
|
|
9790
9786
|
plan.acceptance_criteria = ["Produce a correct and helpful result for the user request."];
|
|
9791
9787
|
}
|
|
@@ -9844,7 +9840,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9844
9840
|
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
9841
|
}
|
|
9846
9842
|
}
|
|
9847
|
-
await
|
|
9843
|
+
await this.fetchImpl(`${this.server}/requests/${requestId}/complete`, {
|
|
9848
9844
|
method: "POST",
|
|
9849
9845
|
headers: this.authHeaders(),
|
|
9850
9846
|
body: JSON.stringify({
|
|
@@ -9875,7 +9871,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
9875
9871
|
correlationId: requestId,
|
|
9876
9872
|
from: eventFrom
|
|
9877
9873
|
});
|
|
9878
|
-
await
|
|
9874
|
+
await this.fetchImpl(`${this.server}/requests/${requestId}/fail`, {
|
|
9879
9875
|
method: "POST",
|
|
9880
9876
|
headers: this.authHeaders(),
|
|
9881
9877
|
body: JSON.stringify({
|
|
@@ -10010,7 +10006,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
10010
10006
|
await this.assistantMessage(requestSessionId, "I could not queue this WorkerPal task. No task was started.", { turnId, correlationId: requestId, from: eventFrom });
|
|
10011
10007
|
this.rememberPersistentMemory("job_enqueue_failed", `enqueue_failed lane=${lane} intent=${plan.intent}`, requestId, requestSessionId);
|
|
10012
10008
|
}
|
|
10013
|
-
await
|
|
10009
|
+
await this.fetchImpl(`${this.server}/requests/${requestId}/complete`, {
|
|
10014
10010
|
method: "POST",
|
|
10015
10011
|
headers: this.authHeaders(),
|
|
10016
10012
|
body: JSON.stringify({
|
|
@@ -10041,7 +10037,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
10041
10037
|
correlationId: requestId,
|
|
10042
10038
|
from: eventFrom
|
|
10043
10039
|
});
|
|
10044
|
-
await
|
|
10040
|
+
await this.fetchImpl(`${this.server}/requests/${requestId}/fail`, {
|
|
10045
10041
|
method: "POST",
|
|
10046
10042
|
headers: this.authHeaders(),
|
|
10047
10043
|
body: JSON.stringify({
|
|
@@ -10056,7 +10052,7 @@ Please reply with the missing details and I will enqueue a follow-up request.` :
|
|
|
10056
10052
|
while (!this.disposed) {
|
|
10057
10053
|
try {
|
|
10058
10054
|
await this.maybeAutoscaleWorkers();
|
|
10059
|
-
const res = await
|
|
10055
|
+
const res = await this.fetchImpl(`${this.server}/requests/claim`, {
|
|
10060
10056
|
method: "POST",
|
|
10061
10057
|
headers: this.authHeaders(),
|
|
10062
10058
|
body: JSON.stringify({ agentId: this.agentId })
|
|
@@ -465,25 +465,9 @@ def _extract_write_globs_from_payload(payload: Optional[Dict[str, Any]]) -> List
|
|
|
465
465
|
|
|
466
466
|
|
|
467
467
|
def _assert_write_allowed(repo: str, path: str, write_globs: Optional[List[str]]) -> None:
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
if not normalized:
|
|
472
|
-
raise RuntimeError(f"Invalid write path for scope enforcement: {path!r}")
|
|
473
|
-
for glob in write_globs:
|
|
474
|
-
pattern = str(glob or "").strip()
|
|
475
|
-
if not pattern:
|
|
476
|
-
continue
|
|
477
|
-
if any(ch in pattern for ch in "*?[]"):
|
|
478
|
-
if fnmatch.fnmatchcase(normalized, pattern):
|
|
479
|
-
return
|
|
480
|
-
continue
|
|
481
|
-
if normalized == pattern or normalized.startswith(pattern + "/"):
|
|
482
|
-
return
|
|
483
|
-
raise RuntimeError(
|
|
484
|
-
"Scope violation: attempted write outside writeGlobs. "
|
|
485
|
-
f"path={normalized!r} write_globs={write_globs!r}"
|
|
486
|
-
)
|
|
468
|
+
# WorkerPal jobs run in isolated sandboxes. Scope hints are used for review
|
|
469
|
+
# relevance, not per-write filesystem enforcement.
|
|
470
|
+
return
|
|
487
471
|
|
|
488
472
|
|
|
489
473
|
def _read_text_file(repo: str, path: str, max_chars: int = 60000) -> str:
|
|
@@ -1587,11 +1571,6 @@ def _broker_run(
|
|
|
1587
1571
|
if expected_targets and changed_paths:
|
|
1588
1572
|
changed_set = {str(p).strip().replace("\\", "/") for p in changed_paths}
|
|
1589
1573
|
expected_set = {str(p).strip().replace("\\", "/") for p in expected_targets}
|
|
1590
|
-
strict_target_match = bool(
|
|
1591
|
-
explicit_target_set
|
|
1592
|
-
and not any(t in {".", "/"} for t in explicit_target_set)
|
|
1593
|
-
and not any(any(ch in t for ch in "*?[]") for t in explicit_target_set)
|
|
1594
|
-
)
|
|
1595
1574
|
matched = any(
|
|
1596
1575
|
_target_hint_matches_changed_path(expected, changed)
|
|
1597
1576
|
for expected in expected_set
|
|
@@ -1602,14 +1581,6 @@ def _broker_run(
|
|
|
1602
1581
|
"Expected one of target paths to change, but observed different files. "
|
|
1603
1582
|
f"expected={sorted(expected_set)} observed={sorted(changed_set)}"
|
|
1604
1583
|
)
|
|
1605
|
-
if strict_target_match:
|
|
1606
|
-
return {
|
|
1607
|
-
"ok": False,
|
|
1608
|
-
"summary": "tool broker failed: changed files do not match explicit target paths",
|
|
1609
|
-
"stdout": stdout + "\n\nChanged files:\n" + "\n".join(f"- {p}" for p in changed_paths),
|
|
1610
|
-
"stderr": msg,
|
|
1611
|
-
"exitCode": 3,
|
|
1612
|
-
}
|
|
1613
1584
|
stdout += "\n\nTarget-path mismatch (heuristic, non-fatal):\n" + msg
|
|
1614
1585
|
if edits_made and not shell_validation_ran:
|
|
1615
1586
|
stdout += (
|
|
@@ -1786,10 +1757,10 @@ def _run_miniswe_task(
|
|
|
1786
1757
|
agent = None
|
|
1787
1758
|
agent_messages: List[Dict[str, Any]] = []
|
|
1788
1759
|
broker_enabled = _tool_broker_enabled(base_url)
|
|
1789
|
-
prefer_broker_for_scoped_writes =
|
|
1760
|
+
prefer_broker_for_scoped_writes = False
|
|
1790
1761
|
ran_primary_broker = False
|
|
1791
1762
|
if prefer_broker_for_scoped_writes and broker_enabled:
|
|
1792
|
-
log.info("Using tool broker shim for
|
|
1763
|
+
log.info("Using tool broker shim for task execution.")
|
|
1793
1764
|
broker_result = _run_broker_with_recovery()
|
|
1794
1765
|
if not bool(broker_result.get("ok")):
|
|
1795
1766
|
return {
|
|
@@ -352,9 +352,10 @@ def _build_path_handling_message(target_paths: List[str], repo: str) -> str:
|
|
|
352
352
|
"- Prefer the repo-relative paths for shell commands.\n"
|
|
353
353
|
"- If FileEditor rejects a repo-relative path, retry with the matching absolute path.\n"
|
|
354
354
|
"- Do not run broad filesystem scans when concrete target paths are listed.\n"
|
|
355
|
-
"
|
|
355
|
+
"- These paths are starting points, not hard write boundaries; edit other behavior-owning files when needed and explain why.\n"
|
|
356
|
+
"Target path hints (repo-relative):\n"
|
|
356
357
|
f"{listed_rel}\n"
|
|
357
|
-
"
|
|
358
|
+
"Target path hints (absolute):\n"
|
|
358
359
|
f"{listed_abs}"
|
|
359
360
|
)
|
|
360
361
|
|
|
@@ -19,7 +19,6 @@ import {
|
|
|
19
19
|
normalizeTargetPath,
|
|
20
20
|
requirementsForValidationCommand,
|
|
21
21
|
sanitizeSourceControlIdentityField,
|
|
22
|
-
validateScopeInvariants,
|
|
23
22
|
type AutonomyComponentArea,
|
|
24
23
|
type SourceControlCommitIdentity,
|
|
25
24
|
type ToolRequirement,
|
|
@@ -2503,12 +2502,21 @@ function buildStageTargets(kind: string, params?: Record<string, unknown>): stri
|
|
|
2503
2502
|
}
|
|
2504
2503
|
}
|
|
2505
2504
|
|
|
2506
|
-
function buildStageCommand(kind: string, params?: Record<string, unknown>): string[] | null {
|
|
2505
|
+
export function buildStageCommand(kind: string, params?: Record<string, unknown>): string[] | null {
|
|
2506
|
+
if (kind === "task.execute") {
|
|
2507
|
+
return [
|
|
2508
|
+
"add",
|
|
2509
|
+
"-A",
|
|
2510
|
+
"--",
|
|
2511
|
+
".",
|
|
2512
|
+
":(exclude)workspace/**",
|
|
2513
|
+
":(exclude)outputs/**",
|
|
2514
|
+
":(exclude).codex",
|
|
2515
|
+
":(exclude).codex/**",
|
|
2516
|
+
];
|
|
2517
|
+
}
|
|
2507
2518
|
const targets = buildStageTargets(kind, params);
|
|
2508
2519
|
if (targets.length === 0) {
|
|
2509
|
-
if (kind === "task.execute") {
|
|
2510
|
-
return ["add", "-A", "--", ".", ":(exclude)workspace/**", ":(exclude)outputs/**"];
|
|
2511
|
-
}
|
|
2512
2520
|
return null;
|
|
2513
2521
|
}
|
|
2514
2522
|
return ["add", "-A", "--", ...targets];
|
|
@@ -3949,13 +3957,10 @@ function taskExecuteOrigin(params: Record<string, unknown>): "autonomy" | "user"
|
|
|
3949
3957
|
return "user";
|
|
3950
3958
|
}
|
|
3951
3959
|
|
|
3952
|
-
function collectWriteScopeIssuesFromChangedPaths(
|
|
3960
|
+
export function collectWriteScopeIssuesFromChangedPaths(
|
|
3953
3961
|
changedPaths: string[],
|
|
3954
3962
|
planning: TaskExecutePlanning,
|
|
3955
3963
|
): string[] {
|
|
3956
|
-
const writeGlobs = toStringArray(planning.scope.writeGlobs ?? []);
|
|
3957
|
-
if (writeGlobs.length === 0) return [];
|
|
3958
|
-
|
|
3959
3964
|
const normalizedChangedPaths = changedPaths
|
|
3960
3965
|
.map((entry) => normalizeStagePath(entry))
|
|
3961
3966
|
.filter((entry): entry is string => Boolean(entry) && entry !== ".");
|
|
@@ -3963,12 +3968,6 @@ function collectWriteScopeIssuesFromChangedPaths(
|
|
|
3963
3968
|
|
|
3964
3969
|
const forbidden = toStringArray(planning.scope.forbiddenGlobs ?? []);
|
|
3965
3970
|
const issues: string[] = [];
|
|
3966
|
-
const outOfScope = normalizedChangedPaths.filter(
|
|
3967
|
-
(path) => !writeGlobs.some((glob) => matchesGlob(path, glob)),
|
|
3968
|
-
);
|
|
3969
|
-
if (outOfScope.length > 0) {
|
|
3970
|
-
issues.push(`modified paths outside writeGlobs: ${outOfScope.join(", ")}`);
|
|
3971
|
-
}
|
|
3972
3971
|
const forbiddenTouched = normalizedChangedPaths.filter((path) =>
|
|
3973
3972
|
forbidden.some((glob) => matchesGlob(path, glob)),
|
|
3974
3973
|
);
|
|
@@ -4105,41 +4104,17 @@ function validateTaskExecutePlanning(
|
|
|
4105
4104
|
reviewAgentAllowsMultiRootScope(options?.reviewAgentResolutionType);
|
|
4106
4105
|
if (origin === "autonomy") {
|
|
4107
4106
|
const declaredComponentArea = asAutonomyComponentArea(options?.autonomyComponentArea);
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
};
|
|
4120
|
-
}
|
|
4121
|
-
if (
|
|
4122
|
-
!allowMultiRootAutonomyScope &&
|
|
4123
|
-
declaredComponentArea &&
|
|
4124
|
-
inferredComponentArea &&
|
|
4125
|
-
declaredComponentArea !== inferredComponentArea
|
|
4126
|
-
) {
|
|
4127
|
-
return {
|
|
4128
|
-
ok: false,
|
|
4129
|
-
message: "task.execute planning.targetPaths do not match autonomy componentArea",
|
|
4130
|
-
};
|
|
4131
|
-
}
|
|
4132
|
-
const validatedScope = validateScopeInvariants(
|
|
4133
|
-
componentArea,
|
|
4134
|
-
normalizedTargetPaths,
|
|
4135
|
-
normalizedWriteGlobs,
|
|
4136
|
-
{ requireWriteGlobs: false, allowMultipleComponentRoots: allowMultiRootAutonomyScope },
|
|
4137
|
-
);
|
|
4138
|
-
if (!validatedScope.ok) {
|
|
4139
|
-
return {
|
|
4140
|
-
ok: false,
|
|
4141
|
-
message: `task.execute scope invariants failed: ${validatedScope.errors.join("; ")}`,
|
|
4142
|
-
};
|
|
4107
|
+
if (!allowMultiRootAutonomyScope && declaredComponentArea) {
|
|
4108
|
+
const inferredComponentArea = deriveAutonomyComponentArea(
|
|
4109
|
+
normalizedTargetPaths,
|
|
4110
|
+
normalizedWriteGlobs,
|
|
4111
|
+
);
|
|
4112
|
+
if (inferredComponentArea && declaredComponentArea !== inferredComponentArea) {
|
|
4113
|
+
return {
|
|
4114
|
+
ok: false,
|
|
4115
|
+
message: "task.execute planning.targetPaths do not match autonomy componentArea",
|
|
4116
|
+
};
|
|
4117
|
+
}
|
|
4143
4118
|
}
|
|
4144
4119
|
} else if (normalizedWriteGlobs.length > 0) {
|
|
4145
4120
|
const uncoveredPaths = normalizedTargetPaths.filter(
|
|
@@ -65,6 +65,8 @@ type WorkerJobResult = JobResult & {
|
|
|
65
65
|
|
|
66
66
|
const DEFAULT_LLM_MODEL = "local-model";
|
|
67
67
|
const CODEX_UNAVAILABLE_WORKER_EXIT_CODE = 86;
|
|
68
|
+
const CODEX_UNAVAILABLE_DOCKER_SHUTDOWN_GRACE_MS = 5_000;
|
|
69
|
+
const CODEX_UNAVAILABLE_WORKER_FORCE_EXIT_MS = 4_000;
|
|
68
70
|
const CONFIG = loadPushPalsConfig();
|
|
69
71
|
const LOG = new Logger("WorkerPals");
|
|
70
72
|
|
|
@@ -360,6 +362,36 @@ function shouldRecycleWorkerForCodexUnavailableFailure(
|
|
|
360
362
|
].some((needle) => text.includes(needle));
|
|
361
363
|
}
|
|
362
364
|
|
|
365
|
+
async function shutdownDockerExecutorBeforeCodexRecycle(
|
|
366
|
+
dockerExecutor: DockerExecutor | null,
|
|
367
|
+
): Promise<void> {
|
|
368
|
+
if (!dockerExecutor) return;
|
|
369
|
+
|
|
370
|
+
let timeout: ReturnType<typeof setTimeout> | null = null;
|
|
371
|
+
let timedOut = false;
|
|
372
|
+
try {
|
|
373
|
+
await Promise.race([
|
|
374
|
+
dockerExecutor.shutdown(),
|
|
375
|
+
new Promise<void>((resolvePromise) => {
|
|
376
|
+
timeout = setTimeout(() => {
|
|
377
|
+
timedOut = true;
|
|
378
|
+
resolvePromise();
|
|
379
|
+
}, CODEX_UNAVAILABLE_DOCKER_SHUTDOWN_GRACE_MS);
|
|
380
|
+
}),
|
|
381
|
+
]);
|
|
382
|
+
} catch (err) {
|
|
383
|
+
console.error(`[WorkerPals] Docker shutdown cleanup failed: ${String(err)}`);
|
|
384
|
+
} finally {
|
|
385
|
+
if (timeout) clearTimeout(timeout);
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (timedOut) {
|
|
389
|
+
console.warn(
|
|
390
|
+
`[WorkerPals] Docker shutdown cleanup exceeded ${CODEX_UNAVAILABLE_DOCKER_SHUTDOWN_GRACE_MS}ms; exiting worker for Codex recycle anyway.`,
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
363
395
|
function parseArgs(): {
|
|
364
396
|
server: string;
|
|
365
397
|
pollMs: number;
|
|
@@ -1667,21 +1699,46 @@ async function workerLoop(
|
|
|
1667
1699
|
}
|
|
1668
1700
|
} finally {
|
|
1669
1701
|
clearInterval(busyHeartbeat);
|
|
1670
|
-
if (
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1702
|
+
if (recycleWorkerAfterJob) {
|
|
1703
|
+
runtimeState.shutdownRequested = true;
|
|
1704
|
+
const forceExitTimer = setTimeout(() => {
|
|
1705
|
+
console.warn(
|
|
1706
|
+
`[WorkerPals] Forcing worker recycle ${CODEX_UNAVAILABLE_WORKER_FORCE_EXIT_MS}ms after Codex backend failure.`,
|
|
1707
|
+
);
|
|
1708
|
+
process.exit(CODEX_UNAVAILABLE_WORKER_EXIT_CODE);
|
|
1709
|
+
}, CODEX_UNAVAILABLE_WORKER_FORCE_EXIT_MS);
|
|
1710
|
+
try {
|
|
1711
|
+
await maybeHeartbeat("offline", null, true);
|
|
1712
|
+
if (directWorktreePath) {
|
|
1713
|
+
await removeIsolatedWorktree(opts.repo, directWorktreePath).catch((err) => {
|
|
1714
|
+
console.error(
|
|
1715
|
+
`[WorkerPals] Failed to remove isolated worktree before Codex recycle: ${String(
|
|
1716
|
+
err,
|
|
1717
|
+
)}`,
|
|
1718
|
+
);
|
|
1719
|
+
});
|
|
1720
|
+
directWorktreePath = null;
|
|
1721
|
+
}
|
|
1722
|
+
await shutdownDockerExecutorBeforeCodexRecycle(dockerExecutor);
|
|
1723
|
+
} finally {
|
|
1724
|
+
clearTimeout(forceExitTimer);
|
|
1725
|
+
process.exit(CODEX_UNAVAILABLE_WORKER_EXIT_CODE);
|
|
1726
|
+
}
|
|
1727
|
+
}
|
|
1728
|
+
if (job.sessionId && result?.cooldownMs && result.cooldownMs > 0) {
|
|
1729
|
+
await transport.queueSessionCommand(
|
|
1730
|
+
job.sessionId,
|
|
1731
|
+
{
|
|
1732
|
+
type: "assistant_message",
|
|
1733
|
+
payload: {
|
|
1734
|
+
text: `WorkerPal is cooling down for ${formatDurationMs(result.cooldownMs)} after transient infrastructure failures.`,
|
|
1735
|
+
},
|
|
1736
|
+
from: `worker:${opts.workerId}`,
|
|
1680
1737
|
},
|
|
1681
|
-
|
|
1682
|
-
|
|
1738
|
+
{ priority: "high" },
|
|
1739
|
+
);
|
|
1683
1740
|
}
|
|
1684
|
-
if (
|
|
1741
|
+
if (result?.cooldownMs && result.cooldownMs > 0) {
|
|
1685
1742
|
const cooldownMs = Math.max(0, Math.floor(result.cooldownMs));
|
|
1686
1743
|
console.warn(
|
|
1687
1744
|
`[WorkerPals] Entering cooldown for ${formatDurationMs(cooldownMs)} after retry exhaustion.`,
|
|
@@ -1697,18 +1754,6 @@ async function workerLoop(
|
|
|
1697
1754
|
console.error(`[WorkerPals] Failed to remove isolated worktree: ${String(err)}`);
|
|
1698
1755
|
});
|
|
1699
1756
|
}
|
|
1700
|
-
if (recycleWorkerAfterJob) {
|
|
1701
|
-
runtimeState.shutdownRequested = true;
|
|
1702
|
-
await maybeHeartbeat("offline", null, true);
|
|
1703
|
-
if (dockerExecutor) {
|
|
1704
|
-
try {
|
|
1705
|
-
await dockerExecutor.shutdown();
|
|
1706
|
-
} catch (err) {
|
|
1707
|
-
console.error(`[WorkerPals] Docker shutdown cleanup failed: ${String(err)}`);
|
|
1708
|
-
}
|
|
1709
|
-
}
|
|
1710
|
-
process.exit(CODEX_UNAVAILABLE_WORKER_EXIT_CODE);
|
|
1711
|
-
}
|
|
1712
1757
|
}
|
|
1713
1758
|
}
|
|
1714
1759
|
}
|
|
@@ -319,7 +319,11 @@ export function validateScopeInvariants(
|
|
|
319
319
|
componentArea: AutonomyComponentArea | null | undefined,
|
|
320
320
|
targetPathsInput: unknown[],
|
|
321
321
|
writeGlobsInput: unknown[],
|
|
322
|
-
options?: {
|
|
322
|
+
options?: {
|
|
323
|
+
requireWriteGlobs?: boolean;
|
|
324
|
+
allowMultipleComponentRoots?: boolean;
|
|
325
|
+
hintsOnly?: boolean;
|
|
326
|
+
},
|
|
323
327
|
): ScopeValidationResult {
|
|
324
328
|
const errors: string[] = [];
|
|
325
329
|
const scopeSeeds = collectScopeSeedPaths(targetPathsInput, writeGlobsInput);
|
|
@@ -327,7 +331,8 @@ export function validateScopeInvariants(
|
|
|
327
331
|
normalizeAutonomyComponentArea(componentArea) ??
|
|
328
332
|
deriveAutonomyComponentArea(targetPathsInput, writeGlobsInput);
|
|
329
333
|
const allowMultipleComponentRoots = options?.allowMultipleComponentRoots === true;
|
|
330
|
-
|
|
334
|
+
const hintsOnly = options?.hintsOnly === true;
|
|
335
|
+
if (!hintsOnly && !normalizedComponentArea && scopeSeeds.length > 1 && !allowMultipleComponentRoots) {
|
|
331
336
|
errors.push(
|
|
332
337
|
`scope spans multiple component roots: ${scopeSeeds.slice(0, 6).join(", ")}`,
|
|
333
338
|
);
|
|
@@ -341,7 +346,7 @@ export function validateScopeInvariants(
|
|
|
341
346
|
errors.push(`invalid target_path: ${String(raw ?? "")}`);
|
|
342
347
|
continue;
|
|
343
348
|
}
|
|
344
|
-
if (rootPrefix && !underRoot(normalized, rootPrefix)) {
|
|
349
|
+
if (!hintsOnly && rootPrefix && !underRoot(normalized, rootPrefix)) {
|
|
345
350
|
errors.push(`target_path outside component root: ${normalized}`);
|
|
346
351
|
continue;
|
|
347
352
|
}
|
|
@@ -362,20 +367,21 @@ export function validateScopeInvariants(
|
|
|
362
367
|
errors.push(`invalid write_glob: ${String(raw ?? "")}`);
|
|
363
368
|
continue;
|
|
364
369
|
}
|
|
365
|
-
if (hasForbiddenBroadGlob(normalized)) {
|
|
370
|
+
if (!hintsOnly && hasForbiddenBroadGlob(normalized)) {
|
|
366
371
|
errors.push(`forbidden broad write_glob: ${normalized}`);
|
|
367
372
|
continue;
|
|
368
373
|
}
|
|
369
374
|
const prefix = literalPrefix(normalized);
|
|
370
|
-
if (!prefix) {
|
|
375
|
+
if (!hintsOnly && !prefix) {
|
|
371
376
|
errors.push(`write_glob literal prefix cannot be empty: ${normalized}`);
|
|
372
377
|
continue;
|
|
373
378
|
}
|
|
374
|
-
if (rootPrefix && !underRoot(prefix, rootPrefix)) {
|
|
379
|
+
if (!hintsOnly && rootPrefix && !underRoot(prefix, rootPrefix)) {
|
|
375
380
|
errors.push(`write_glob outside component root: ${normalized}`);
|
|
376
381
|
continue;
|
|
377
382
|
}
|
|
378
383
|
if (
|
|
384
|
+
!hintsOnly &&
|
|
379
385
|
!normalizedTargetPaths.some(
|
|
380
386
|
(targetPath) => targetPath === prefix || targetPath.startsWith(`${prefix}/`),
|
|
381
387
|
)
|
|
@@ -393,13 +399,13 @@ export function validateScopeInvariants(
|
|
|
393
399
|
errors.push("write_globs must be provided and non-empty");
|
|
394
400
|
}
|
|
395
401
|
|
|
396
|
-
if (normalizedTargetPaths.length > 0 && normalizedWriteGlobs.length > 0) {
|
|
402
|
+
if (!hintsOnly && normalizedTargetPaths.length > 0 && normalizedWriteGlobs.length > 0) {
|
|
397
403
|
for (const targetPath of normalizedTargetPaths) {
|
|
398
404
|
const covered = normalizedWriteGlobs.some((glob) => matchesGlob(targetPath, glob));
|
|
399
405
|
if (!covered) errors.push(`target_path not covered by write_globs: ${targetPath}`);
|
|
400
406
|
}
|
|
401
407
|
}
|
|
402
|
-
if (!normalizedComponentArea && !allowMultipleComponentRoots) {
|
|
408
|
+
if (!hintsOnly && !normalizedComponentArea && !allowMultipleComponentRoots) {
|
|
403
409
|
errors.push("component_area could not be derived from scope");
|
|
404
410
|
}
|
|
405
411
|
|
|
@@ -53,6 +53,7 @@ export interface CommunicationManagerOptions {
|
|
|
53
53
|
sessionId: string;
|
|
54
54
|
from: string;
|
|
55
55
|
authToken?: string | null;
|
|
56
|
+
fetchImpl?: typeof fetch;
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
export class CommunicationManager {
|
|
@@ -60,12 +61,14 @@ export class CommunicationManager {
|
|
|
60
61
|
private readonly sessionId: string;
|
|
61
62
|
private readonly from: string;
|
|
62
63
|
private readonly authToken: string | null;
|
|
64
|
+
private readonly fetchImpl: typeof fetch;
|
|
63
65
|
|
|
64
66
|
constructor(opts: CommunicationManagerOptions) {
|
|
65
67
|
this.serverUrl = opts.serverUrl;
|
|
66
68
|
this.sessionId = opts.sessionId;
|
|
67
69
|
this.from = opts.from;
|
|
68
70
|
this.authToken = opts.authToken ?? null;
|
|
71
|
+
this.fetchImpl = opts.fetchImpl ?? fetch;
|
|
69
72
|
}
|
|
70
73
|
|
|
71
74
|
private headers(): Record<string, string> {
|
|
@@ -121,7 +124,7 @@ export class CommunicationManager {
|
|
|
121
124
|
if (meta.turnId) body.turnId = meta.turnId;
|
|
122
125
|
if (meta.parentId) body.parentId = meta.parentId;
|
|
123
126
|
|
|
124
|
-
const response = await
|
|
127
|
+
const response = await this.fetchImpl(this.commandUrl(sessionId), {
|
|
125
128
|
method: "POST",
|
|
126
129
|
headers: this.headers(),
|
|
127
130
|
body: JSON.stringify(body),
|
|
@@ -1929,7 +1929,7 @@ export function loadPushPalsConfig(options: LoadOptions = {}): PushPalsConfig {
|
|
|
1929
1929
|
),
|
|
1930
1930
|
allowReadAnywhere:
|
|
1931
1931
|
parseBoolEnv("REMOTEBUDDY_AUTONOMY_ALLOW_READ_ANYWHERE") ??
|
|
1932
|
-
asBoolean(remoteAutonomyNode.allow_read_anywhere,
|
|
1932
|
+
asBoolean(remoteAutonomyNode.allow_read_anywhere, true),
|
|
1933
1933
|
prFeedbackCommentRows: Math.max(
|
|
1934
1934
|
1,
|
|
1935
1935
|
Math.min(
|
|
@@ -1 +1 @@
|
|
|
1
|
-
Completion requirement:
|
|
1
|
+
Completion requirement: solve the requested task before setting done=true. Target paths are relevance hints; edit other behavior-owning files when needed and explain why.
|
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
Target path hints:
|
|
2
2
|
{{targets_block}}
|
|
@@ -11,7 +11,10 @@ Non-negotiable runtime invariants:
|
|
|
11
11
|
|
|
12
12
|
Execution rules:
|
|
13
13
|
|
|
14
|
-
- Keep edits minimal, correct, and
|
|
14
|
+
- Keep edits minimal, correct, and relevant to the requested task.
|
|
15
|
+
- You have repo-wide read/write access inside an isolated WorkerPal sandbox. Target paths and write globs are starting-point/relevance hints, not hard write boundaries.
|
|
16
|
+
- If the hinted file is a thin wrapper or the behavior lives elsewhere, edit the behavior-owning file(s) needed to solve the task and explain the scope expansion in your final response.
|
|
17
|
+
- Avoid irrelevant sprawl; the review agent will judge whether changed files are necessary for the requested outcome.
|
|
15
18
|
- Read relevant files before editing, then run focused validation.
|
|
16
19
|
- Use direct commands without shell wrappers. Prefer plain commands like `git diff -- path`, `git add <path>`, `git status --porcelain`, and `pwd`.
|
|
17
20
|
- Do not wrap commands in `/bin/bash -lc`, `sh -lc`, `cmd /c`, or `powershell -Command`, and avoid pipelines, `awk`, heredocs, or multi-command shell snippets unless they are truly unavoidable.
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
You are PushPals WorkerPal running inside OpenHands.
|
|
2
2
|
|
|
3
|
-
Operate with strict
|
|
3
|
+
Operate with strict relevance:
|
|
4
4
|
- Focus only on the current user task.
|
|
5
5
|
- If the task is a question, answer directly and do not edit files.
|
|
6
6
|
- If code/file changes are requested, implement them end-to-end.
|
|
7
7
|
- Reuse existing repository conventions and avoid unrelated refactors/docs.
|
|
8
|
+
- Treat target paths and write globs as starting-point/relevance hints, not hard write boundaries.
|
|
9
|
+
- If the behavior lives outside the hinted files, edit the owning files and explain the scope expansion.
|
|
8
10
|
|
|
9
11
|
Execution loop:
|
|
10
12
|
- Prefer concrete execution over planning chatter.
|
|
@@ -3,7 +3,8 @@ You are PushPals WorkerPal running inside OpenHands.
|
|
|
3
3
|
Execution rules:
|
|
4
4
|
|
|
5
5
|
- Focus only on the task below.
|
|
6
|
-
- Keep changes minimal, correct, and
|
|
6
|
+
- Keep changes minimal, correct, and relevant to the request.
|
|
7
|
+
- Target paths and write globs are starting-point/relevance hints, not hard write boundaries; if the behavior lives elsewhere, edit the owning files and explain why.
|
|
7
8
|
- Read relevant files before editing.
|
|
8
9
|
- Reuse existing project conventions and tooling.
|
|
9
10
|
- If the task is a question/explanation, answer directly and do not edit files.
|
|
@@ -6,7 +6,7 @@ Your mission:
|
|
|
6
6
|
|
|
7
7
|
- Take the user (or RemoteBuddy) request and fully execute it end-to-end.
|
|
8
8
|
- You are responsible for breaking the work down into concrete subtasks, completing them, validating, reviewing your own changes, and preparing a high-quality commit message when the work is ready.
|
|
9
|
-
- You have full read/write access within the assigned repository only; do not access files outside that repository.
|
|
9
|
+
- You have full read/write access within the assigned repository sandbox only; do not access files outside that repository. Target paths and write globs are starting-point/relevance hints, not hard write boundaries.
|
|
10
10
|
|
|
11
11
|
Mindset:
|
|
12
12
|
|
|
@@ -64,7 +64,7 @@ Execution workflow (you MUST follow this):
|
|
|
64
64
|
- Performance: no unnecessary work on UI thread, no extra network calls, no large bundles
|
|
65
65
|
- Cross-platform: iOS/Android/Web differences guarded appropriately
|
|
66
66
|
- Security: no secret leakage, safe networking defaults, no unsafe shell usage
|
|
67
|
-
- Make any final polish edits that improve clarity without
|
|
67
|
+
- Make any final polish edits that improve clarity without drifting from the task.
|
|
68
68
|
|
|
69
69
|
7. Prepare to commit (when appropriate)
|
|
70
70
|
- When the work is ready, produce a detailed commit message (do NOT actually commit unless your system explicitly allows it).
|