@pushpalsdev/cli 1.0.5 → 1.0.7
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 +788 -64
- package/package.json +3 -2
- package/runtime/.env.example +73 -0
- package/runtime/configs/backend.toml +79 -0
- package/runtime/configs/default.toml +259 -0
- package/runtime/configs/dev.toml +2 -0
- package/runtime/configs/local.example.toml +124 -0
- package/runtime/prompts/localbuddy/local_quick_reply_json_system_suffix.md +2 -0
- package/runtime/prompts/localbuddy/local_quick_reply_system_prompt.md +10 -0
- package/runtime/prompts/localbuddy/local_quick_reply_user_prompt.md +2 -0
- package/runtime/prompts/localbuddy/localbuddy_planner_git_diff_section.md +3 -0
- package/runtime/prompts/localbuddy/localbuddy_planner_git_status_section.md +3 -0
- package/runtime/prompts/localbuddy/localbuddy_planner_output_contract.md +5 -0
- package/runtime/prompts/localbuddy/localbuddy_planner_user_prompt.md +2 -0
- package/runtime/prompts/localbuddy/localbuddy_system_prompt.md +110 -0
- package/runtime/prompts/remotebuddy/autonomy_ideation_system_prompt.md +60 -0
- package/runtime/prompts/remotebuddy/autonomy_planning_system_prompt.md +5 -0
- package/runtime/prompts/remotebuddy/autonomy_scoring_system_prompt.md +6 -0
- package/runtime/prompts/remotebuddy/codex_adapter_json_requirements.md +1 -0
- package/runtime/prompts/remotebuddy/codex_adapter_json_schema_intro.md +1 -0
- package/runtime/prompts/remotebuddy/codex_adapter_max_tokens_line.md +1 -0
- package/runtime/prompts/remotebuddy/codex_adapter_prompt_template.md +14 -0
- package/runtime/prompts/remotebuddy/context_packer_condensed_history_system_prompt.md +1 -0
- package/runtime/prompts/remotebuddy/context_packer_system_prompt.md +1 -0
- package/runtime/prompts/remotebuddy/context_packer_user_prompt.md +11 -0
- package/runtime/prompts/remotebuddy/fallback_file_system_prompt.md +1 -0
- package/runtime/prompts/remotebuddy/fallback_file_user_prompt.md +4 -0
- package/runtime/prompts/remotebuddy/planner_post_system_prompt.md +2 -0
- package/runtime/prompts/remotebuddy/planner_repair_suffix_prompt.md +1 -0
- package/runtime/prompts/remotebuddy/planner_repair_user_prompt.md +7 -0
- package/runtime/prompts/remotebuddy/remotebuddy_system_prompt.md +109 -0
- package/runtime/prompts/review_agent/fix_job_intro_line.md +1 -0
- package/runtime/prompts/review_agent/merge_conflict_context_intro_line.md +1 -0
- package/runtime/prompts/review_agent/merge_conflict_instruction.md +4 -0
- package/runtime/prompts/review_agent/review_prompt_template.md +18 -0
- package/runtime/prompts/review_agent/reviewer.md +39 -0
- package/runtime/prompts/shared/post_system_prompt.md +62 -0
- package/runtime/prompts/workerpals/codex_quality_critic_instruction_prompt.md +14 -0
- package/runtime/prompts/workerpals/commit_message_prompt.md +36 -0
- package/runtime/prompts/workerpals/commit_message_user_prompt.md +7 -0
- package/runtime/prompts/workerpals/miniswe_broker_system_prompt.md +33 -0
- package/runtime/prompts/workerpals/miniswe_broker_task_prompt.md +5 -0
- package/runtime/prompts/workerpals/miniswe_completion_requirement.md +1 -0
- package/runtime/prompts/workerpals/miniswe_context_compaction_retry_prompt.md +1 -0
- package/runtime/prompts/workerpals/miniswe_explicit_targets_block.md +2 -0
- package/runtime/prompts/workerpals/miniswe_recovery_guidance_base.md +4 -0
- package/runtime/prompts/workerpals/miniswe_recovery_guidance_blocker_line.md +1 -0
- package/runtime/prompts/workerpals/miniswe_strict_tool_use_guidance.md +6 -0
- package/runtime/prompts/workerpals/miniswe_supplemental_guidance_section.md +2 -0
- package/runtime/prompts/workerpals/miniswe_timeout_note.md +1 -0
- package/runtime/prompts/workerpals/miniswe_toolcall_retry_guidance.md +1 -0
- package/runtime/prompts/workerpals/openai_codex_default_system_prompt.md +4 -0
- package/runtime/prompts/workerpals/openai_codex_instruction_wrapper.md +5 -0
- package/runtime/prompts/workerpals/openai_codex_runtime_policy_appendix.md +5 -0
- package/runtime/prompts/workerpals/openai_codex_supplemental_guidance_section.md +2 -0
- package/runtime/prompts/workerpals/openai_codex_task_execute_system_prompt.md +12 -0
- package/runtime/prompts/workerpals/openhands_minimal_security_policy.j2 +8 -0
- package/runtime/prompts/workerpals/openhands_minimal_system_prompt.j2 +20 -0
- package/runtime/prompts/workerpals/openhands_strict_tool_use_message.md +1 -0
- package/runtime/prompts/workerpals/openhands_supplemental_guidance_message.md +2 -0
- package/runtime/prompts/workerpals/openhands_task_execute_fallback_system_prompt.md +1 -0
- package/runtime/prompts/workerpals/openhands_task_execute_system_prompt.md +21 -0
- package/runtime/prompts/workerpals/openhands_task_user_prompt.md +6 -0
- package/runtime/prompts/workerpals/openhands_timeout_note.md +1 -0
- package/runtime/prompts/workerpals/pr_description.md +42 -0
- package/runtime/prompts/workerpals/task_quality_critic_system_prompt.md +9 -0
- package/runtime/prompts/workerpals/task_quality_critic_user_prompt.md +17 -0
- package/runtime/prompts/workerpals/workerpals_system_prompt.md +115 -0
- package/runtime/protocol/schemas/approvals.schema.json +6 -0
- package/runtime/protocol/schemas/envelope.schema.json +96 -0
- package/runtime/protocol/schemas/events.schema.json +679 -0
- package/runtime/protocol/schemas/http.schema.json +50 -0
- package/runtime/vision.example.md +191 -0
package/dist/pushpals-cli.js
CHANGED
|
@@ -2,10 +2,14 @@
|
|
|
2
2
|
// @bun
|
|
3
3
|
|
|
4
4
|
// ../../scripts/pushpals-cli.ts
|
|
5
|
-
import { appendFileSync, chmodSync, cpSync, existsSync as
|
|
6
|
-
import { dirname, join as join2, resolve as
|
|
5
|
+
import { appendFileSync, chmodSync, cpSync, existsSync as existsSync3, mkdirSync, readFileSync as readFileSync3, writeFileSync } from "fs";
|
|
6
|
+
import { dirname, join as join2, resolve as resolve3 } from "path";
|
|
7
7
|
import { createInterface } from "readline";
|
|
8
8
|
|
|
9
|
+
// ../shared/src/client_preflight.ts
|
|
10
|
+
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
11
|
+
import { relative, resolve as resolve2 } from "path";
|
|
12
|
+
|
|
9
13
|
// ../shared/src/config.ts
|
|
10
14
|
import { existsSync, readFileSync } from "fs";
|
|
11
15
|
import { join, resolve, isAbsolute } from "path";
|
|
@@ -739,15 +743,327 @@ function loadPushPalsConfig(options = {}) {
|
|
|
739
743
|
return config;
|
|
740
744
|
}
|
|
741
745
|
|
|
746
|
+
// ../shared/src/vision.ts
|
|
747
|
+
var SECTION_HEADING_RE = /^##\s+(\d+)\)\s+(.+?)\s*$/;
|
|
748
|
+
var ONE_SENTENCE_PROMPT_RE = /^\>\s*\*\*One sentence:\*\*\s*(.+)\s*$/i;
|
|
749
|
+
var BLOCKQUOTE_RE = /^\>\s*(.+?)\s*$/;
|
|
750
|
+
function toLines(markdown) {
|
|
751
|
+
return String(markdown ?? "").replace(/\r\n/g, `
|
|
752
|
+
`).split(`
|
|
753
|
+
`);
|
|
754
|
+
}
|
|
755
|
+
function extractOneSentence(lines) {
|
|
756
|
+
let expectNextBlockquoteSentence = false;
|
|
757
|
+
for (const line of lines) {
|
|
758
|
+
const marker = line.match(ONE_SENTENCE_PROMPT_RE);
|
|
759
|
+
if (marker) {
|
|
760
|
+
const inline = marker[1].trim();
|
|
761
|
+
if (inline)
|
|
762
|
+
return inline;
|
|
763
|
+
expectNextBlockquoteSentence = true;
|
|
764
|
+
continue;
|
|
765
|
+
}
|
|
766
|
+
const block = line.match(BLOCKQUOTE_RE);
|
|
767
|
+
if (expectNextBlockquoteSentence) {
|
|
768
|
+
if (!block)
|
|
769
|
+
continue;
|
|
770
|
+
const text = block[1].trim();
|
|
771
|
+
if (!text)
|
|
772
|
+
continue;
|
|
773
|
+
if (/^Example:/i.test(text))
|
|
774
|
+
continue;
|
|
775
|
+
return text;
|
|
776
|
+
}
|
|
777
|
+
}
|
|
778
|
+
for (const line of lines) {
|
|
779
|
+
const block = line.match(BLOCKQUOTE_RE);
|
|
780
|
+
if (!block)
|
|
781
|
+
continue;
|
|
782
|
+
const text = block[1].trim();
|
|
783
|
+
if (!text)
|
|
784
|
+
continue;
|
|
785
|
+
if (/^\*\*One sentence:\*\*/i.test(text))
|
|
786
|
+
continue;
|
|
787
|
+
if (/^Example:/i.test(text))
|
|
788
|
+
continue;
|
|
789
|
+
return text;
|
|
790
|
+
}
|
|
791
|
+
return "";
|
|
792
|
+
}
|
|
793
|
+
function parseVisionDoc(markdown) {
|
|
794
|
+
const lines = toLines(markdown);
|
|
795
|
+
const sections = [];
|
|
796
|
+
let currentNumber = "";
|
|
797
|
+
let currentTitle = "";
|
|
798
|
+
let currentBody = [];
|
|
799
|
+
const flushCurrent = () => {
|
|
800
|
+
if (!currentNumber)
|
|
801
|
+
return;
|
|
802
|
+
sections.push({
|
|
803
|
+
number: currentNumber,
|
|
804
|
+
title: currentTitle,
|
|
805
|
+
markdown: currentBody.join(`
|
|
806
|
+
`).trim()
|
|
807
|
+
});
|
|
808
|
+
currentNumber = "";
|
|
809
|
+
currentTitle = "";
|
|
810
|
+
currentBody = [];
|
|
811
|
+
};
|
|
812
|
+
for (const line of lines) {
|
|
813
|
+
const heading = line.match(SECTION_HEADING_RE);
|
|
814
|
+
if (heading) {
|
|
815
|
+
flushCurrent();
|
|
816
|
+
currentNumber = heading[1];
|
|
817
|
+
currentTitle = heading[2].trim();
|
|
818
|
+
continue;
|
|
819
|
+
}
|
|
820
|
+
if (currentNumber) {
|
|
821
|
+
currentBody.push(line);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
flushCurrent();
|
|
825
|
+
const sectionByNumber = {};
|
|
826
|
+
for (const section of sections) {
|
|
827
|
+
if (!sectionByNumber[section.number]) {
|
|
828
|
+
sectionByNumber[section.number] = section;
|
|
829
|
+
}
|
|
830
|
+
}
|
|
831
|
+
return {
|
|
832
|
+
oneSentence: extractOneSentence(lines),
|
|
833
|
+
sections,
|
|
834
|
+
sectionByNumber
|
|
835
|
+
};
|
|
836
|
+
}
|
|
837
|
+
function validateVisionDocStructure(markdown) {
|
|
838
|
+
const parsed = parseVisionDoc(markdown);
|
|
839
|
+
const missingSectionNumbers = [];
|
|
840
|
+
const errors = [];
|
|
841
|
+
if (!parsed.oneSentence) {
|
|
842
|
+
errors.push('Missing one-sentence vision line (expected near the top as a blockquote after "**One sentence:**").');
|
|
843
|
+
}
|
|
844
|
+
return {
|
|
845
|
+
ok: errors.length === 0,
|
|
846
|
+
sectionCount: parsed.sections.length,
|
|
847
|
+
hasOneSentence: Boolean(parsed.oneSentence),
|
|
848
|
+
missingSectionNumbers,
|
|
849
|
+
errors
|
|
850
|
+
};
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
// ../shared/src/client_preflight.ts
|
|
854
|
+
function runtimeHasConfigDir(runtimeRoot, dirName) {
|
|
855
|
+
const dirPath = resolve2(runtimeRoot, dirName);
|
|
856
|
+
return existsSync2(resolve2(dirPath, "default.toml")) || existsSync2(resolve2(dirPath, "local.example.toml")) || existsSync2(resolve2(dirPath, "local.toml"));
|
|
857
|
+
}
|
|
858
|
+
function resolveClientConfigDir(projectRoot, runtimeRoot, explicitConfigDir) {
|
|
859
|
+
if (explicitConfigDir && explicitConfigDir.trim()) {
|
|
860
|
+
return resolve2(explicitConfigDir);
|
|
861
|
+
}
|
|
862
|
+
const runtimeCanonical = resolve2(runtimeRoot, "configs");
|
|
863
|
+
if (runtimeHasConfigDir(runtimeRoot, "configs")) {
|
|
864
|
+
return runtimeCanonical;
|
|
865
|
+
}
|
|
866
|
+
const runtimeLegacy = resolve2(runtimeRoot, "config");
|
|
867
|
+
if (runtimeHasConfigDir(runtimeRoot, "config")) {
|
|
868
|
+
return runtimeLegacy;
|
|
869
|
+
}
|
|
870
|
+
const projectCanonical = resolve2(projectRoot, "configs");
|
|
871
|
+
if (runtimeHasConfigDir(projectRoot, "configs")) {
|
|
872
|
+
return projectCanonical;
|
|
873
|
+
}
|
|
874
|
+
const projectLegacy = resolve2(projectRoot, "config");
|
|
875
|
+
if (runtimeHasConfigDir(projectRoot, "config")) {
|
|
876
|
+
return projectLegacy;
|
|
877
|
+
}
|
|
878
|
+
return runtimeCanonical;
|
|
879
|
+
}
|
|
880
|
+
function toDisplayPath(currentRoot, pathValue) {
|
|
881
|
+
const rel = relative(currentRoot, pathValue);
|
|
882
|
+
if (!rel || rel === "")
|
|
883
|
+
return ".";
|
|
884
|
+
if (rel.startsWith(".."))
|
|
885
|
+
return pathValue;
|
|
886
|
+
return rel.replace(/\\/g, "/");
|
|
887
|
+
}
|
|
888
|
+
function quotePowerShell(pathValue) {
|
|
889
|
+
if (/^[A-Za-z0-9_./\\:-]+$/.test(pathValue))
|
|
890
|
+
return pathValue;
|
|
891
|
+
return `'${pathValue.replace(/'/g, "''")}'`;
|
|
892
|
+
}
|
|
893
|
+
function quoteBash(pathValue) {
|
|
894
|
+
if (/^[A-Za-z0-9_./\\:-]+$/.test(pathValue))
|
|
895
|
+
return pathValue;
|
|
896
|
+
return "'" + pathValue.replace(/'/g, `'"'"'`) + "'";
|
|
897
|
+
}
|
|
898
|
+
function buildCopyCommands(workspaceRoot, sourcePath, destPath) {
|
|
899
|
+
const displaySource = toDisplayPath(workspaceRoot, sourcePath);
|
|
900
|
+
const displayDest = toDisplayPath(workspaceRoot, destPath);
|
|
901
|
+
return {
|
|
902
|
+
windowsPowerShell: `Copy-Item ${quotePowerShell(displaySource)} ${quotePowerShell(displayDest)}`,
|
|
903
|
+
bash: `cp ${quoteBash(displaySource)} ${quoteBash(displayDest)}`
|
|
904
|
+
};
|
|
905
|
+
}
|
|
906
|
+
function evaluateClientRuntimePreflight(options) {
|
|
907
|
+
const projectRoot = resolve2(options.projectRoot);
|
|
908
|
+
const runtimeRoot = resolve2(options.runtimeRoot ?? projectRoot);
|
|
909
|
+
const configDir = resolveClientConfigDir(projectRoot, runtimeRoot, options.configDir);
|
|
910
|
+
const visionTemplateRoot = resolve2(options.visionTemplateRoot ?? runtimeRoot);
|
|
911
|
+
const config = options.config ?? loadPushPalsConfig({
|
|
912
|
+
projectRoot,
|
|
913
|
+
configDir,
|
|
914
|
+
reload: true
|
|
915
|
+
});
|
|
916
|
+
const issues = [];
|
|
917
|
+
const envPath = resolve2(runtimeRoot, ".env");
|
|
918
|
+
if (!existsSync2(envPath)) {
|
|
919
|
+
const envExamplePath = resolve2(runtimeRoot, ".env.example");
|
|
920
|
+
issues.push({
|
|
921
|
+
code: "missing_env_file",
|
|
922
|
+
message: `Missing required local env file: ${toDisplayPath(projectRoot, envPath)}.`,
|
|
923
|
+
copyCommands: existsSync2(envExamplePath) ? buildCopyCommands(projectRoot, envExamplePath, envPath) : undefined
|
|
924
|
+
});
|
|
925
|
+
}
|
|
926
|
+
const localTomlPath = resolve2(runtimeRoot, "configs", "local.toml");
|
|
927
|
+
const legacyLocalTomlPath = resolve2(runtimeRoot, "config", "local.toml");
|
|
928
|
+
if (!existsSync2(localTomlPath) && !existsSync2(legacyLocalTomlPath)) {
|
|
929
|
+
const localExamplePath = resolve2(runtimeRoot, "configs", "local.example.toml");
|
|
930
|
+
issues.push({
|
|
931
|
+
code: "missing_local_toml",
|
|
932
|
+
message: `Missing required local config file: ${toDisplayPath(projectRoot, localTomlPath)}.`,
|
|
933
|
+
copyCommands: existsSync2(localExamplePath) ? buildCopyCommands(projectRoot, localExamplePath, localTomlPath) : undefined
|
|
934
|
+
});
|
|
935
|
+
}
|
|
936
|
+
const autonomyEnabled = Boolean(config.remotebuddy.autonomy.enabled);
|
|
937
|
+
if (!autonomyEnabled) {
|
|
938
|
+
return {
|
|
939
|
+
ok: issues.length === 0,
|
|
940
|
+
projectRoot,
|
|
941
|
+
runtimeRoot,
|
|
942
|
+
config,
|
|
943
|
+
issues,
|
|
944
|
+
autonomyEnabled,
|
|
945
|
+
visionSummary: null
|
|
946
|
+
};
|
|
947
|
+
}
|
|
948
|
+
const visionPath = resolve2(projectRoot, "vision.md");
|
|
949
|
+
const visionTemplatePath = resolve2(visionTemplateRoot, "vision.example.md");
|
|
950
|
+
if (!existsSync2(visionPath)) {
|
|
951
|
+
issues.push({
|
|
952
|
+
code: "missing_vision_doc",
|
|
953
|
+
message: "Missing required autonomy vision file: vision.md " + "(required when remotebuddy.autonomy.enabled=true).",
|
|
954
|
+
copyCommands: existsSync2(visionTemplatePath) ? buildCopyCommands(projectRoot, visionTemplatePath, visionPath) : undefined
|
|
955
|
+
});
|
|
956
|
+
return {
|
|
957
|
+
ok: false,
|
|
958
|
+
projectRoot,
|
|
959
|
+
runtimeRoot,
|
|
960
|
+
config,
|
|
961
|
+
issues,
|
|
962
|
+
autonomyEnabled,
|
|
963
|
+
visionSummary: null
|
|
964
|
+
};
|
|
965
|
+
}
|
|
966
|
+
let rawVision = "";
|
|
967
|
+
try {
|
|
968
|
+
rawVision = readFileSync2(visionPath, "utf8");
|
|
969
|
+
} catch (err) {
|
|
970
|
+
issues.push({
|
|
971
|
+
code: "unreadable_vision_doc",
|
|
972
|
+
message: `Autonomy vision preflight failed: could not read vision.md.`,
|
|
973
|
+
detail: String(err)
|
|
974
|
+
});
|
|
975
|
+
return {
|
|
976
|
+
ok: false,
|
|
977
|
+
projectRoot,
|
|
978
|
+
runtimeRoot,
|
|
979
|
+
config,
|
|
980
|
+
issues,
|
|
981
|
+
autonomyEnabled,
|
|
982
|
+
visionSummary: null
|
|
983
|
+
};
|
|
984
|
+
}
|
|
985
|
+
const visionText = rawVision.trim();
|
|
986
|
+
if (!visionText) {
|
|
987
|
+
issues.push({
|
|
988
|
+
code: "empty_vision_doc",
|
|
989
|
+
message: "Autonomy vision preflight failed: vision.md is empty.",
|
|
990
|
+
detail: "Add repository vision/goals before startup."
|
|
991
|
+
});
|
|
992
|
+
return {
|
|
993
|
+
ok: false,
|
|
994
|
+
projectRoot,
|
|
995
|
+
runtimeRoot,
|
|
996
|
+
config,
|
|
997
|
+
issues,
|
|
998
|
+
autonomyEnabled,
|
|
999
|
+
visionSummary: null
|
|
1000
|
+
};
|
|
1001
|
+
}
|
|
1002
|
+
const validation = validateVisionDocStructure(visionText);
|
|
1003
|
+
if (!validation.ok) {
|
|
1004
|
+
issues.push({
|
|
1005
|
+
code: "invalid_vision_doc",
|
|
1006
|
+
message: "Autonomy vision preflight failed: vision.md is invalid.",
|
|
1007
|
+
detail: validation.errors.join(" ")
|
|
1008
|
+
});
|
|
1009
|
+
return {
|
|
1010
|
+
ok: false,
|
|
1011
|
+
projectRoot,
|
|
1012
|
+
runtimeRoot,
|
|
1013
|
+
config,
|
|
1014
|
+
issues,
|
|
1015
|
+
autonomyEnabled,
|
|
1016
|
+
visionSummary: null
|
|
1017
|
+
};
|
|
1018
|
+
}
|
|
1019
|
+
return {
|
|
1020
|
+
ok: issues.length === 0,
|
|
1021
|
+
projectRoot,
|
|
1022
|
+
runtimeRoot,
|
|
1023
|
+
config,
|
|
1024
|
+
issues,
|
|
1025
|
+
autonomyEnabled,
|
|
1026
|
+
visionSummary: {
|
|
1027
|
+
path: toDisplayPath(projectRoot, visionPath),
|
|
1028
|
+
chars: visionText.length,
|
|
1029
|
+
sectionCount: validation.sectionCount,
|
|
1030
|
+
validation
|
|
1031
|
+
}
|
|
1032
|
+
};
|
|
1033
|
+
}
|
|
1034
|
+
function formatClientRuntimePreflightLines(result, prefix) {
|
|
1035
|
+
const normalizedPrefix = prefix.trim();
|
|
1036
|
+
const lines = [];
|
|
1037
|
+
if (result.ok) {
|
|
1038
|
+
if (result.visionSummary) {
|
|
1039
|
+
lines.push(`${normalizedPrefix} Autonomy preflight: loaded ${result.visionSummary.path} ` + `(${result.visionSummary.chars} chars, ${result.visionSummary.sectionCount} section(s)).`);
|
|
1040
|
+
}
|
|
1041
|
+
return lines;
|
|
1042
|
+
}
|
|
1043
|
+
for (const issue of result.issues) {
|
|
1044
|
+
lines.push(`${normalizedPrefix} ${issue.message}`);
|
|
1045
|
+
if (issue.detail) {
|
|
1046
|
+
lines.push(`${normalizedPrefix} ${issue.detail}`);
|
|
1047
|
+
}
|
|
1048
|
+
if (issue.copyCommands) {
|
|
1049
|
+
lines.push(`${normalizedPrefix} Windows (PowerShell): ${issue.copyCommands.windowsPowerShell}`);
|
|
1050
|
+
lines.push(`${normalizedPrefix} Linux/macOS (bash): ${issue.copyCommands.bash}`);
|
|
1051
|
+
}
|
|
1052
|
+
}
|
|
1053
|
+
return lines;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
742
1056
|
// ../../scripts/pushpals-cli.ts
|
|
743
1057
|
var DEFAULT_MONITOR_PORT = 8081;
|
|
744
1058
|
var MONITOR_SCAN_PORTS = 32;
|
|
1059
|
+
var MONITOR_POLL_MS = 2000;
|
|
745
1060
|
var HTTP_TIMEOUT_MS = 2500;
|
|
746
1061
|
var LOCALBUDDY_TIMEOUT_MS = 4000;
|
|
747
1062
|
var SSE_RECONNECT_MS = 1500;
|
|
748
1063
|
var DEFAULT_RUNTIME_BOOT_TIMEOUT_MS = 90000;
|
|
749
1064
|
var DEFAULT_RUNTIME_BOOT_POLL_MS = 1000;
|
|
750
1065
|
var DEFAULT_SERVER_BOOT_TIMEOUT_MS = 20000;
|
|
1066
|
+
var DEFAULT_SERVICE_STABILITY_GRACE_MS = 4000;
|
|
751
1067
|
var GITHUB_OWNER = "PushPalsDev";
|
|
752
1068
|
var GITHUB_REPO = "pushpals";
|
|
753
1069
|
var GITHUB_API_URL = `https://api.github.com/repos/${GITHUB_OWNER}/${GITHUB_REPO}`;
|
|
@@ -853,11 +1169,14 @@ function parsePositiveInt(value, fallback) {
|
|
|
853
1169
|
return parsed;
|
|
854
1170
|
}
|
|
855
1171
|
function normalizePath(value) {
|
|
856
|
-
const normalized =
|
|
1172
|
+
const normalized = resolve3(value).replace(/\\/g, "/").replace(/\/+$/, "");
|
|
857
1173
|
if (process.platform === "win32")
|
|
858
1174
|
return normalized.toLowerCase();
|
|
859
1175
|
return normalized;
|
|
860
1176
|
}
|
|
1177
|
+
function jsonHtmlBootstrap(value) {
|
|
1178
|
+
return JSON.stringify(value).replace(/</g, "\\u003c");
|
|
1179
|
+
}
|
|
861
1180
|
async function runGit(args, cwd) {
|
|
862
1181
|
const proc = Bun.spawn(["git", ...args], {
|
|
863
1182
|
cwd,
|
|
@@ -878,11 +1197,39 @@ async function resolveCurrentGitRepoRoot(cwd) {
|
|
|
878
1197
|
const root = await runGit(["rev-parse", "--show-toplevel"], cwd);
|
|
879
1198
|
if (!root.ok || !root.stdout)
|
|
880
1199
|
return null;
|
|
881
|
-
return
|
|
1200
|
+
return resolve3(root.stdout);
|
|
882
1201
|
}
|
|
883
1202
|
function resolveDefaultRuntimeRoot() {
|
|
884
1203
|
const home = process.env.USERPROFILE || process.env.HOME || process.cwd();
|
|
885
|
-
return
|
|
1204
|
+
return resolve3(home, ".pushpals", "runtime");
|
|
1205
|
+
}
|
|
1206
|
+
function buildRuntimeAssetSource(root, protocolSchemasDir) {
|
|
1207
|
+
return {
|
|
1208
|
+
root,
|
|
1209
|
+
envExamplePath: join2(root, ".env.example"),
|
|
1210
|
+
visionExamplePath: join2(root, "vision.example.md"),
|
|
1211
|
+
configsDir: join2(root, "configs"),
|
|
1212
|
+
promptsDir: join2(root, "prompts"),
|
|
1213
|
+
protocolSchemasDir
|
|
1214
|
+
};
|
|
1215
|
+
}
|
|
1216
|
+
function isCompleteRuntimeAssetSource(source) {
|
|
1217
|
+
return existsSync3(source.envExamplePath) && existsSync3(source.visionExamplePath) && existsSync3(join2(source.configsDir, "default.toml")) && existsSync3(source.promptsDir) && existsSync3(join2(source.protocolSchemasDir, "envelope.schema.json")) && existsSync3(join2(source.protocolSchemasDir, "events.schema.json"));
|
|
1218
|
+
}
|
|
1219
|
+
function resolveBundledRuntimeAssetSource() {
|
|
1220
|
+
const candidates = [
|
|
1221
|
+
buildRuntimeAssetSource(resolve3(import.meta.dir, "..", "runtime"), resolve3(import.meta.dir, "..", "runtime", "protocol", "schemas")),
|
|
1222
|
+
buildRuntimeAssetSource(resolve3(import.meta.dir, ".."), resolve3(import.meta.dir, "..", "packages", "protocol", "src", "schemas")),
|
|
1223
|
+
buildRuntimeAssetSource(resolve3(import.meta.dir, "..", "packages", "cli", "runtime"), resolve3(import.meta.dir, "..", "packages", "cli", "runtime", "protocol", "schemas"))
|
|
1224
|
+
];
|
|
1225
|
+
for (const candidate of candidates) {
|
|
1226
|
+
if (isCompleteRuntimeAssetSource(candidate))
|
|
1227
|
+
return candidate;
|
|
1228
|
+
}
|
|
1229
|
+
return null;
|
|
1230
|
+
}
|
|
1231
|
+
function repoLooksLikePushPalsSourceCheckout(repoRoot) {
|
|
1232
|
+
return existsSync3(join2(repoRoot, "configs", "default.toml")) || existsSync3(join2(repoRoot, "config", "default.toml"));
|
|
886
1233
|
}
|
|
887
1234
|
function parseSemverFromPackageVersion(value) {
|
|
888
1235
|
const raw = String(value ?? "").trim();
|
|
@@ -932,18 +1279,56 @@ async function resolveRuntimeReleaseTag(explicitTag) {
|
|
|
932
1279
|
}
|
|
933
1280
|
}
|
|
934
1281
|
function writeTextFileIfMissing(pathValue, text) {
|
|
935
|
-
if (
|
|
1282
|
+
if (existsSync3(pathValue))
|
|
936
1283
|
return;
|
|
937
1284
|
mkdirSync(dirname(pathValue), { recursive: true });
|
|
938
1285
|
writeFileSync(pathValue, text, "utf8");
|
|
939
1286
|
}
|
|
940
|
-
function
|
|
941
|
-
|
|
942
|
-
|
|
1287
|
+
function copyRuntimeAssetBundle(source, runtimeRoot, force) {
|
|
1288
|
+
mkdirSync(runtimeRoot, { recursive: true });
|
|
1289
|
+
cpSync(source.envExamplePath, join2(runtimeRoot, ".env.example"), {
|
|
1290
|
+
force,
|
|
1291
|
+
errorOnExist: false
|
|
1292
|
+
});
|
|
1293
|
+
cpSync(source.visionExamplePath, join2(runtimeRoot, "vision.example.md"), {
|
|
1294
|
+
force,
|
|
1295
|
+
errorOnExist: false
|
|
1296
|
+
});
|
|
1297
|
+
cpSync(source.configsDir, join2(runtimeRoot, "configs"), {
|
|
1298
|
+
recursive: true,
|
|
1299
|
+
force,
|
|
1300
|
+
errorOnExist: false
|
|
1301
|
+
});
|
|
1302
|
+
cpSync(source.promptsDir, join2(runtimeRoot, "prompts"), {
|
|
1303
|
+
recursive: true,
|
|
1304
|
+
force,
|
|
1305
|
+
errorOnExist: false
|
|
1306
|
+
});
|
|
1307
|
+
cpSync(source.protocolSchemasDir, join2(runtimeRoot, "protocol", "schemas"), {
|
|
1308
|
+
recursive: true,
|
|
1309
|
+
force,
|
|
1310
|
+
errorOnExist: false
|
|
1311
|
+
});
|
|
1312
|
+
}
|
|
1313
|
+
function copyBundledRuntimeAssets(runtimeRoot, force = true) {
|
|
1314
|
+
const bundledSource = resolveBundledRuntimeAssetSource();
|
|
1315
|
+
if (!bundledSource)
|
|
943
1316
|
return false;
|
|
944
|
-
|
|
1317
|
+
copyRuntimeAssetBundle(bundledSource, runtimeRoot, force);
|
|
945
1318
|
return true;
|
|
946
1319
|
}
|
|
1320
|
+
function seedRuntimePreflightAssets(runtimeRoot) {
|
|
1321
|
+
copyBundledRuntimeAssets(runtimeRoot, false);
|
|
1322
|
+
writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
|
|
1323
|
+
`);
|
|
1324
|
+
const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
|
|
1325
|
+
if (existsSync3(localExamplePath)) {
|
|
1326
|
+
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync3(localExamplePath, "utf8"));
|
|
1327
|
+
} else {
|
|
1328
|
+
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
|
|
1329
|
+
`);
|
|
1330
|
+
}
|
|
1331
|
+
}
|
|
947
1332
|
async function fetchTextFromUrl(url, timeoutMs = 20000) {
|
|
948
1333
|
const response = await fetchWithTimeout(url, { headers: GITHUB_HEADERS }, timeoutMs);
|
|
949
1334
|
if (!response.ok) {
|
|
@@ -958,7 +1343,7 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
|
|
|
958
1343
|
throw new Error(`Failed to fetch runtime source tree for ${tag} (HTTP ${treeResponse.status})`);
|
|
959
1344
|
}
|
|
960
1345
|
const treePayload = await treeResponse.json();
|
|
961
|
-
const paths = (treePayload.tree ?? []).filter((entry) => entry.type === "blob" && typeof entry.path === "string").map((entry) => String(entry.path)).filter((pathValue) => pathValue === ".env.example" || pathValue.startsWith("configs/") || pathValue.startsWith("prompts/") || pathValue.startsWith("packages/protocol/src/schemas/"));
|
|
1346
|
+
const paths = (treePayload.tree ?? []).filter((entry) => entry.type === "blob" && typeof entry.path === "string").map((entry) => String(entry.path)).filter((pathValue) => pathValue === ".env.example" || pathValue === "vision.example.md" || pathValue.startsWith("configs/") || pathValue.startsWith("prompts/") || pathValue.startsWith("packages/protocol/src/schemas/"));
|
|
962
1347
|
if (paths.length === 0) {
|
|
963
1348
|
throw new Error(`Runtime source tree for ${tag} did not include prompts/config assets`);
|
|
964
1349
|
}
|
|
@@ -973,14 +1358,14 @@ async function downloadRuntimeAssetsFromSourceTag(runtimeRoot, tag) {
|
|
|
973
1358
|
}
|
|
974
1359
|
async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
|
|
975
1360
|
const markerPath = join2(runtimeRoot, ".runtime-assets-tag");
|
|
976
|
-
const currentTag =
|
|
1361
|
+
const currentTag = existsSync3(markerPath) ? readFileSync3(markerPath, "utf8").trim() : "";
|
|
977
1362
|
const protocolSchemasDir = join2(runtimeRoot, "protocol", "schemas");
|
|
978
|
-
const hasProtocolSchemas =
|
|
979
|
-
const hasAssets =
|
|
1363
|
+
const hasProtocolSchemas = existsSync3(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync3(join2(protocolSchemasDir, "events.schema.json"));
|
|
1364
|
+
const hasAssets = existsSync3(join2(runtimeRoot, ".env.example")) && existsSync3(join2(runtimeRoot, "vision.example.md")) && existsSync3(join2(runtimeRoot, "configs", "default.toml")) && existsSync3(join2(runtimeRoot, "prompts")) && hasProtocolSchemas;
|
|
980
1365
|
if (!hasAssets || currentTag !== runtimeTag) {
|
|
981
1366
|
copyBundledRuntimeAssets(runtimeRoot);
|
|
982
|
-
const hasProtocolSchemasAfterCopy =
|
|
983
|
-
const hasAssetsAfterCopy =
|
|
1367
|
+
const hasProtocolSchemasAfterCopy = existsSync3(join2(protocolSchemasDir, "envelope.schema.json")) && existsSync3(join2(protocolSchemasDir, "events.schema.json"));
|
|
1368
|
+
const hasAssetsAfterCopy = existsSync3(join2(runtimeRoot, ".env.example")) && existsSync3(join2(runtimeRoot, "vision.example.md")) && existsSync3(join2(runtimeRoot, "configs", "default.toml")) && existsSync3(join2(runtimeRoot, "prompts")) && hasProtocolSchemasAfterCopy;
|
|
984
1369
|
if (!hasAssetsAfterCopy) {
|
|
985
1370
|
await downloadRuntimeAssetsFromSourceTag(runtimeRoot, runtimeTag);
|
|
986
1371
|
}
|
|
@@ -990,25 +1375,77 @@ async function ensureRuntimeAssets(runtimeRoot, runtimeTag) {
|
|
|
990
1375
|
writeTextFileIfMissing(join2(runtimeRoot, ".env"), `# Local PushPals runtime environment
|
|
991
1376
|
`);
|
|
992
1377
|
const localExamplePath = join2(runtimeRoot, "configs", "local.example.toml");
|
|
993
|
-
if (
|
|
994
|
-
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"),
|
|
1378
|
+
if (existsSync3(localExamplePath)) {
|
|
1379
|
+
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), readFileSync3(localExamplePath, "utf8"));
|
|
995
1380
|
} else {
|
|
996
1381
|
writeTextFileIfMissing(join2(runtimeRoot, "configs", "local.toml"), `# Local PushPals runtime overrides
|
|
997
1382
|
`);
|
|
998
1383
|
}
|
|
999
1384
|
}
|
|
1385
|
+
function resolveDeferredRuntimeTagHint(explicitTag) {
|
|
1386
|
+
return String(explicitTag || process.env.PUSHPALS_RUNTIME_TAG || "").trim();
|
|
1387
|
+
}
|
|
1388
|
+
async function prepareCliRuntime(opts) {
|
|
1389
|
+
const runtimeRoot = resolve3(opts.runtimeRoot || process.env.PUSHPALS_RUNTIME_ROOT || resolveDefaultRuntimeRoot());
|
|
1390
|
+
if (repoLooksLikePushPalsSourceCheckout(opts.repoRoot)) {
|
|
1391
|
+
return {
|
|
1392
|
+
runtimeRoot,
|
|
1393
|
+
runtimeTag: "",
|
|
1394
|
+
runtimePreflight: evaluateClientRuntimePreflight({
|
|
1395
|
+
projectRoot: opts.repoRoot
|
|
1396
|
+
}),
|
|
1397
|
+
preflightUsesEmbeddedRuntime: false
|
|
1398
|
+
};
|
|
1399
|
+
}
|
|
1400
|
+
seedRuntimePreflightAssets(runtimeRoot);
|
|
1401
|
+
return {
|
|
1402
|
+
runtimeRoot,
|
|
1403
|
+
runtimeTag: resolveDeferredRuntimeTagHint(opts.runtimeTag),
|
|
1404
|
+
runtimePreflight: evaluateClientRuntimePreflight({
|
|
1405
|
+
projectRoot: opts.repoRoot,
|
|
1406
|
+
runtimeRoot,
|
|
1407
|
+
visionTemplateRoot: runtimeRoot
|
|
1408
|
+
}),
|
|
1409
|
+
preflightUsesEmbeddedRuntime: true
|
|
1410
|
+
};
|
|
1411
|
+
}
|
|
1412
|
+
function emitCliRuntimePreflight(result) {
|
|
1413
|
+
const lines = formatClientRuntimePreflightLines(result, "[pushpals]");
|
|
1414
|
+
if (result.ok) {
|
|
1415
|
+
for (const line of lines)
|
|
1416
|
+
console.log(line);
|
|
1417
|
+
return;
|
|
1418
|
+
}
|
|
1419
|
+
for (const line of lines)
|
|
1420
|
+
console.error(line);
|
|
1421
|
+
}
|
|
1000
1422
|
function runtimeBinaryFilename(serviceName, platformKey) {
|
|
1001
1423
|
const serviceToken = serviceName === "source_control_manager" ? "source-control-manager" : serviceName;
|
|
1002
1424
|
const extension = platformKey.startsWith("windows-") ? ".exe" : "";
|
|
1003
1425
|
return `pushpals-runtime-${serviceToken}-${platformKey}${extension}`;
|
|
1004
1426
|
}
|
|
1427
|
+
function buildEmbeddedRuntimeEnv(baseEnv, opts) {
|
|
1428
|
+
const useRuntimeConfig = opts.useRuntimeConfig !== false;
|
|
1429
|
+
return {
|
|
1430
|
+
...baseEnv,
|
|
1431
|
+
PUSHPALS_REPO_ROOT_OVERRIDE: opts.repoRoot,
|
|
1432
|
+
PUSHPALS_PROJECT_ROOT_OVERRIDE: opts.repoRoot,
|
|
1433
|
+
...useRuntimeConfig ? {
|
|
1434
|
+
PUSHPALS_CONFIG_DIR_OVERRIDE: join2(opts.runtimeRoot, "configs"),
|
|
1435
|
+
PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.runtimeRoot
|
|
1436
|
+
} : {
|
|
1437
|
+
PUSHPALS_PROMPTS_ROOT_OVERRIDE: opts.repoRoot
|
|
1438
|
+
},
|
|
1439
|
+
PUSHPALS_PROTOCOL_SCHEMAS_DIR: join2(opts.runtimeRoot, "protocol", "schemas")
|
|
1440
|
+
};
|
|
1441
|
+
}
|
|
1005
1442
|
function timestampFileToken() {
|
|
1006
1443
|
return new Date().toISOString().replace(/[:.]/g, "-");
|
|
1007
1444
|
}
|
|
1008
1445
|
function readLogTail(logPath, maxLines = 40) {
|
|
1009
|
-
if (!
|
|
1446
|
+
if (!existsSync3(logPath))
|
|
1010
1447
|
return "";
|
|
1011
|
-
const raw =
|
|
1448
|
+
const raw = readFileSync3(logPath, "utf8");
|
|
1012
1449
|
const lines = raw.split(/\r?\n/).map((line) => line.trimEnd()).filter((line) => line.length > 0);
|
|
1013
1450
|
if (lines.length === 0)
|
|
1014
1451
|
return "";
|
|
@@ -1042,7 +1479,7 @@ async function ensureRuntimeBinaries(runtimeRoot, runtimeTag) {
|
|
|
1042
1479
|
runtimeBinaries.sourceControlManager
|
|
1043
1480
|
];
|
|
1044
1481
|
for (const binaryPath of requiredAssets) {
|
|
1045
|
-
if (
|
|
1482
|
+
if (existsSync3(binaryPath))
|
|
1046
1483
|
continue;
|
|
1047
1484
|
const assetName = binaryPath.split(/[\\/]/).pop() || "";
|
|
1048
1485
|
await downloadBinaryAsset(runtimeTag, assetName, binaryPath);
|
|
@@ -1112,10 +1549,25 @@ function spawnRuntimeService(name, command, cwd, env, logPath) {
|
|
|
1112
1549
|
function stopRuntimeServices(services) {
|
|
1113
1550
|
for (const service of services) {
|
|
1114
1551
|
try {
|
|
1115
|
-
service.proc.
|
|
1552
|
+
if (process.platform === "win32" && typeof service.proc.pid === "number" && service.proc.pid > 0) {
|
|
1553
|
+
Bun.spawnSync(["taskkill", "/PID", String(service.proc.pid), "/T", "/F"], {
|
|
1554
|
+
stdin: "ignore",
|
|
1555
|
+
stdout: "ignore",
|
|
1556
|
+
stderr: "ignore"
|
|
1557
|
+
});
|
|
1558
|
+
} else {
|
|
1559
|
+
service.proc.kill();
|
|
1560
|
+
}
|
|
1116
1561
|
} catch {}
|
|
1117
1562
|
}
|
|
1118
1563
|
}
|
|
1564
|
+
async function repoHasRemote(repoRoot, remote) {
|
|
1565
|
+
const normalizedRemote = remote.trim();
|
|
1566
|
+
if (!normalizedRemote)
|
|
1567
|
+
return false;
|
|
1568
|
+
const result = await runGit(["remote", "get-url", normalizedRemote], repoRoot);
|
|
1569
|
+
return result.ok && Boolean(result.stdout);
|
|
1570
|
+
}
|
|
1119
1571
|
async function probeServer(serverUrl) {
|
|
1120
1572
|
try {
|
|
1121
1573
|
const response = await fetchWithTimeout(`${serverUrl}/healthz`, {}, HTTP_TIMEOUT_MS);
|
|
@@ -1162,21 +1614,22 @@ async function probeLocalBuddy(localAgentUrl, authToken) {
|
|
|
1162
1614
|
return await fetchJsonWithTimeout(`${localAgentUrl}/healthz`, { headers: authHeaders(authToken) }, LOCALBUDDY_TIMEOUT_MS);
|
|
1163
1615
|
}
|
|
1164
1616
|
async function autoStartRuntimeServices(opts) {
|
|
1165
|
-
const
|
|
1166
|
-
const
|
|
1167
|
-
const
|
|
1168
|
-
await ensureRuntimeAssets(runtimeRoot, runtimeTag);
|
|
1617
|
+
const { runtimePreflight } = opts.preparedRuntime;
|
|
1618
|
+
const runtimeRoot = opts.preparedRuntime.runtimeRoot;
|
|
1619
|
+
const runtimeTag = opts.preparedRuntime.runtimeTag || await resolveRuntimeReleaseTag(opts.requestedRuntimeTag);
|
|
1169
1620
|
console.log(`[pushpals] LocalBuddy unavailable. Auto-starting runtime for repo: ${opts.repoRoot}`);
|
|
1170
1621
|
console.log(`[pushpals] runtimeRoot=${runtimeRoot}`);
|
|
1171
1622
|
console.log(`[pushpals] runtimeTag=${runtimeTag}`);
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1623
|
+
if (!runtimePreflight.ok) {
|
|
1624
|
+
throw new Error("Embedded runtime preflight failed.");
|
|
1625
|
+
}
|
|
1626
|
+
await ensureRuntimeAssets(runtimeRoot, runtimeTag);
|
|
1627
|
+
const runtimeBinaries = await ensureRuntimeBinaries(runtimeRoot, runtimeTag);
|
|
1628
|
+
const runtimeEnv = buildEmbeddedRuntimeEnv(process.env, {
|
|
1629
|
+
repoRoot: opts.repoRoot,
|
|
1630
|
+
runtimeRoot,
|
|
1631
|
+
useRuntimeConfig: opts.preparedRuntime.preflightUsesEmbeddedRuntime
|
|
1632
|
+
});
|
|
1180
1633
|
const services = [];
|
|
1181
1634
|
const runToken = timestampFileToken();
|
|
1182
1635
|
const logDir = join2(runtimeRoot, "logs", "bootstrap");
|
|
@@ -1224,11 +1677,14 @@ ${tail}` : ""}`);
|
|
|
1224
1677
|
services.push(remotebuddyService);
|
|
1225
1678
|
console.log(`[pushpals] remotebuddy log: ${remotebuddyService.logPath}`);
|
|
1226
1679
|
const scmHealthy = await probeSourceControlManager(opts.sourceControlManagerPort);
|
|
1227
|
-
|
|
1680
|
+
const scmRemoteAvailable = await repoHasRemote(opts.repoRoot, opts.sourceControlManagerRemote);
|
|
1681
|
+
if (!scmHealthy && scmRemoteAvailable) {
|
|
1228
1682
|
console.log("[pushpals] Starting embedded SourceControlManager...");
|
|
1229
1683
|
const sourceControlManagerService = spawnRuntimeService("source_control_manager", [runtimeBinaries.sourceControlManager, "--skip-clean-check"], opts.repoRoot, runtimeEnv, logPathFor("source_control_manager"));
|
|
1230
1684
|
services.push(sourceControlManagerService);
|
|
1231
1685
|
console.log(`[pushpals] source_control_manager log: ${sourceControlManagerService.logPath}`);
|
|
1686
|
+
} else if (!scmRemoteAvailable) {
|
|
1687
|
+
console.log(`[pushpals] Repo has no git remote "${opts.sourceControlManagerRemote}"; skipping embedded SourceControlManager.`);
|
|
1232
1688
|
} else {
|
|
1233
1689
|
console.log("[pushpals] SourceControlManager already healthy; skipping embedded start.");
|
|
1234
1690
|
}
|
|
@@ -1256,6 +1712,19 @@ ${tail}` : ""}`);
|
|
|
1256
1712
|
}
|
|
1257
1713
|
const health = await probeLocalBuddy(opts.localAgentUrl, opts.authToken);
|
|
1258
1714
|
if (health?.ok) {
|
|
1715
|
+
const stabilityDeadline = Date.now() + DEFAULT_SERVICE_STABILITY_GRACE_MS;
|
|
1716
|
+
while (Date.now() < stabilityDeadline) {
|
|
1717
|
+
for (const service of services) {
|
|
1718
|
+
if (!service.exited)
|
|
1719
|
+
continue;
|
|
1720
|
+
const tail = readLogTail(service.logPath);
|
|
1721
|
+
stopRuntimeServices(services);
|
|
1722
|
+
throw new Error(`Embedded ${service.name} exited immediately after bootstrap (code=${service.exitCode ?? "unknown"}). ` + `See ${service.logPath}${tail ? `
|
|
1723
|
+
--- ${service.name} log tail ---
|
|
1724
|
+
${tail}` : ""}`);
|
|
1725
|
+
}
|
|
1726
|
+
await Bun.sleep(250);
|
|
1727
|
+
}
|
|
1259
1728
|
console.log("[pushpals] Embedded runtime is ready.");
|
|
1260
1729
|
return services;
|
|
1261
1730
|
}
|
|
@@ -1265,10 +1734,10 @@ ${tail}` : ""}`);
|
|
|
1265
1734
|
throw new Error(`Timed out waiting for LocalBuddy at ${opts.localAgentUrl} after ${DEFAULT_RUNTIME_BOOT_TIMEOUT_MS}ms`);
|
|
1266
1735
|
}
|
|
1267
1736
|
function readCliState(pathValue) {
|
|
1268
|
-
if (!
|
|
1737
|
+
if (!existsSync3(pathValue))
|
|
1269
1738
|
return {};
|
|
1270
1739
|
try {
|
|
1271
|
-
const raw =
|
|
1740
|
+
const raw = readFileSync3(pathValue, "utf8");
|
|
1272
1741
|
const parsed = JSON.parse(raw);
|
|
1273
1742
|
if (!parsed || typeof parsed !== "object")
|
|
1274
1743
|
return {};
|
|
@@ -1309,17 +1778,221 @@ async function looksLikeMonitoringHub(url) {
|
|
|
1309
1778
|
return false;
|
|
1310
1779
|
}
|
|
1311
1780
|
}
|
|
1312
|
-
|
|
1313
|
-
const
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1781
|
+
function buildEmbeddedMonitoringHubHtml(opts) {
|
|
1782
|
+
const bootstrap = jsonHtmlBootstrap(opts);
|
|
1783
|
+
return `<!doctype html>
|
|
1784
|
+
<html lang="en">
|
|
1785
|
+
<head>
|
|
1786
|
+
<meta charset="utf-8" />
|
|
1787
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
1788
|
+
<title>PushPals CLI Monitor</title>
|
|
1789
|
+
<style>
|
|
1790
|
+
:root { color-scheme: dark; --bg:#08111b; --panel:#112235; --panel2:#16324a; --line:#2b5876; --fg:#edf6ff; --muted:#90b5d6; --accent:#58d8c3; --warn:#ffbf5f; --bad:#ff7f7f; }
|
|
1791
|
+
* { box-sizing:border-box; }
|
|
1792
|
+
body { margin:0; font-family:Consolas, "SFMono-Regular", monospace; background:radial-gradient(circle at top, #0d2233, var(--bg) 56%); color:var(--fg); }
|
|
1793
|
+
main { max-width:1200px; margin:0 auto; padding:24px; }
|
|
1794
|
+
h1,h2 { margin:0 0 12px; }
|
|
1795
|
+
p { color:var(--muted); }
|
|
1796
|
+
.row { display:grid; gap:16px; margin-top:16px; }
|
|
1797
|
+
.cards { grid-template-columns:repeat(auto-fit,minmax(180px,1fr)); }
|
|
1798
|
+
.panels { grid-template-columns:repeat(auto-fit,minmax(320px,1fr)); }
|
|
1799
|
+
.card, .panel { border:1px solid var(--line); background:linear-gradient(180deg,var(--panel),var(--panel2)); border-radius:16px; padding:16px; box-shadow:0 12px 40px rgba(0,0,0,.22); }
|
|
1800
|
+
.label { font-size:12px; letter-spacing:.12em; text-transform:uppercase; color:var(--muted); margin-bottom:10px; }
|
|
1801
|
+
.value { font-size:32px; font-weight:700; color:var(--accent); }
|
|
1802
|
+
.sub { margin-top:8px; color:var(--muted); white-space:pre-wrap; word-break:break-word; }
|
|
1803
|
+
.list { display:grid; gap:10px; margin-top:12px; }
|
|
1804
|
+
.item { border:1px solid rgba(88,216,195,.18); border-radius:12px; padding:12px; background:rgba(8,17,27,.42); }
|
|
1805
|
+
.meta { display:flex; flex-wrap:wrap; gap:8px; margin:12px 0 0; }
|
|
1806
|
+
.pill { border:1px solid var(--line); border-radius:999px; padding:6px 10px; color:var(--muted); }
|
|
1807
|
+
a { color:var(--accent); }
|
|
1808
|
+
</style>
|
|
1809
|
+
</head>
|
|
1810
|
+
<body>
|
|
1811
|
+
<main>
|
|
1812
|
+
<h1>PushPals CLI Monitor</h1>
|
|
1813
|
+
<p>Lightweight embedded monitor for CLI-managed runtimes.</p>
|
|
1814
|
+
<div class="meta" id="meta"></div>
|
|
1815
|
+
<section class="row cards" id="cards"></section>
|
|
1816
|
+
<section class="row panels">
|
|
1817
|
+
<div class="panel">
|
|
1818
|
+
<h2>Requests</h2>
|
|
1819
|
+
<div id="requests" class="list"></div>
|
|
1820
|
+
</div>
|
|
1821
|
+
<div class="panel">
|
|
1822
|
+
<h2>Jobs</h2>
|
|
1823
|
+
<div id="jobs" class="list"></div>
|
|
1824
|
+
</div>
|
|
1825
|
+
<div class="panel">
|
|
1826
|
+
<h2>Completions</h2>
|
|
1827
|
+
<div id="completions" class="list"></div>
|
|
1828
|
+
</div>
|
|
1829
|
+
</section>
|
|
1830
|
+
</main>
|
|
1831
|
+
<script>
|
|
1832
|
+
const boot = ${bootstrap};
|
|
1833
|
+
const pollMs = ${MONITOR_POLL_MS};
|
|
1834
|
+
const metaEl = document.getElementById("meta");
|
|
1835
|
+
const cardsEl = document.getElementById("cards");
|
|
1836
|
+
const requestsEl = document.getElementById("requests");
|
|
1837
|
+
const jobsEl = document.getElementById("jobs");
|
|
1838
|
+
const completionsEl = document.getElementById("completions");
|
|
1839
|
+
|
|
1840
|
+
function esc(value) {
|
|
1841
|
+
return String(value ?? "")
|
|
1842
|
+
.replace(/&/g, "&")
|
|
1843
|
+
.replace(/</g, "<")
|
|
1844
|
+
.replace(/>/g, ">");
|
|
1845
|
+
}
|
|
1846
|
+
|
|
1847
|
+
async function fetchJson(path) {
|
|
1848
|
+
const res = await fetch(path, { cache: "no-store" });
|
|
1849
|
+
if (!res.ok) throw new Error(path + " -> HTTP " + res.status);
|
|
1850
|
+
return await res.json();
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1853
|
+
function setList(target, rows, emptyLabel, formatter) {
|
|
1854
|
+
if (!Array.isArray(rows) || rows.length === 0) {
|
|
1855
|
+
target.innerHTML = '<div class="item">' + esc(emptyLabel) + "</div>";
|
|
1856
|
+
return;
|
|
1857
|
+
}
|
|
1858
|
+
target.innerHTML = rows.map((row) => '<div class="item">' + formatter(row) + "</div>").join("");
|
|
1859
|
+
}
|
|
1860
|
+
|
|
1861
|
+
function renderStatus(status) {
|
|
1862
|
+
const workers = status?.workers ?? {};
|
|
1863
|
+
const queues = status?.queues ?? {};
|
|
1864
|
+
const runtime = status?.runtime ?? {};
|
|
1865
|
+
const repo = status?.repo ?? {};
|
|
1866
|
+
const llmUsage = status?.llmUsage ?? {};
|
|
1867
|
+
const cards = [
|
|
1868
|
+
{ label: "Server uptime", value: Math.round((Number(runtime.uptimeMs ?? 0) / 60000)) + "m", sub: runtime.startedAt ?? "unknown" },
|
|
1869
|
+
{ label: "Workers online", value: String(workers.online ?? 0), sub: "busy " + String(workers.busy ?? 0) + " | idle " + String(workers.idle ?? 0) },
|
|
1870
|
+
{ label: "Pending requests", value: String(queues.requests?.pending ?? 0), sub: "claimed " + String(queues.requests?.claimed ?? 0) },
|
|
1871
|
+
{ label: "Pending jobs", value: String(queues.jobs?.pending ?? 0), sub: "claimed " + String(queues.jobs?.claimed ?? 0) },
|
|
1872
|
+
{ label: "Completions", value: String(queues.completions?.pending ?? 0), sub: "processed " + String(queues.completions?.processed ?? 0) },
|
|
1873
|
+
{ label: "LLM usage (24h)", value: String(llmUsage.totalTokens ?? 0), sub: "calls " + String(llmUsage.totalCalls ?? 0) }
|
|
1874
|
+
];
|
|
1875
|
+
cardsEl.innerHTML = cards.map((card) => '<div class="card"><div class="label">' + esc(card.label) + '</div><div class="value">' + esc(card.value) + '</div><div class="sub">' + esc(card.sub) + '</div></div>').join("");
|
|
1876
|
+
metaEl.innerHTML = [
|
|
1877
|
+
'<span class="pill">server ' + esc(boot.serverUrl) + '</span>',
|
|
1878
|
+
'<span class="pill">localbuddy ' + esc(boot.localAgentUrl) + '</span>',
|
|
1879
|
+
'<span class="pill">session ' + esc(boot.sessionId) + '</span>',
|
|
1880
|
+
'<span class="pill">repo ' + esc(repo?.root ?? repo?.remoteUrl ?? "current repo") + '</span>'
|
|
1881
|
+
].join("");
|
|
1882
|
+
}
|
|
1883
|
+
|
|
1884
|
+
function render() {
|
|
1885
|
+
Promise.all([
|
|
1886
|
+
fetchJson('/api/status'),
|
|
1887
|
+
fetchJson('/api/requests'),
|
|
1888
|
+
fetchJson('/api/jobs'),
|
|
1889
|
+
fetchJson('/api/completions')
|
|
1890
|
+
]).then(([status, requests, jobs, completions]) => {
|
|
1891
|
+
renderStatus(status);
|
|
1892
|
+
setList(requestsEl, requests?.requests?.slice(0, 8), 'No requests', (row) =>
|
|
1893
|
+
'<strong>' + esc(row?.priority ?? 'request') + '</strong><div class="sub">' +
|
|
1894
|
+
esc((row?.status ?? 'unknown') + ' | ' + (row?.id ?? '')) + '</div><div class="sub">' +
|
|
1895
|
+
esc(String(row?.prompt ?? '').slice(0, 220)) + '</div>');
|
|
1896
|
+
setList(jobsEl, jobs?.jobs?.slice(0, 8), 'No jobs', (row) =>
|
|
1897
|
+
'<strong>' + esc(row?.kind ?? 'job') + '</strong><div class="sub">' +
|
|
1898
|
+
esc((row?.status ?? 'unknown') + ' | worker ' + (row?.workerId ?? '--')) + '</div><div class="sub">' +
|
|
1899
|
+
esc((row?.summary ?? row?.error ?? row?.id ?? '').slice(0, 220)) + '</div>');
|
|
1900
|
+
setList(completionsEl, completions?.completions?.slice(0, 8), 'No completions', (row) =>
|
|
1901
|
+
'<strong>' + esc(row?.status ?? 'completion') + '</strong><div class="sub">' +
|
|
1902
|
+
esc((row?.jobId ?? '') + ' | ' + (row?.commitSha ?? '')) + '</div><div class="sub">' +
|
|
1903
|
+
esc((row?.message ?? '').slice(0, 220)) + '</div>');
|
|
1904
|
+
}).catch((err) => {
|
|
1905
|
+
cardsEl.innerHTML = '<div class="card"><div class="label">Monitor error</div><div class="sub">' + esc(err?.message ?? err) + '</div></div>';
|
|
1906
|
+
});
|
|
1907
|
+
}
|
|
1908
|
+
|
|
1909
|
+
render();
|
|
1910
|
+
setInterval(render, pollMs);
|
|
1911
|
+
</script>
|
|
1912
|
+
</body>
|
|
1913
|
+
</html>`;
|
|
1914
|
+
}
|
|
1915
|
+
async function proxyMonitoringHubRequest(serverUrl, authToken, pathValue) {
|
|
1916
|
+
const target = `${serverUrl}${pathValue}`;
|
|
1917
|
+
const upstream = await fetchWithTimeout(target, { headers: authHeaders(authToken) }, 1e4);
|
|
1918
|
+
const body = await upstream.text();
|
|
1919
|
+
return new Response(body, {
|
|
1920
|
+
status: upstream.status,
|
|
1921
|
+
headers: {
|
|
1922
|
+
"content-type": String(upstream.headers.get("content-type") ?? "application/json"),
|
|
1923
|
+
"cache-control": "no-store"
|
|
1924
|
+
}
|
|
1925
|
+
});
|
|
1926
|
+
}
|
|
1927
|
+
async function startEmbeddedMonitoringHub(opts) {
|
|
1928
|
+
const html = buildEmbeddedMonitoringHubHtml({
|
|
1929
|
+
serverUrl: opts.serverUrl,
|
|
1930
|
+
localAgentUrl: opts.localAgentUrl,
|
|
1931
|
+
sessionId: opts.sessionId
|
|
1932
|
+
});
|
|
1933
|
+
const candidatePorts = Array.from({ length: MONITOR_SCAN_PORTS }, (_, index) => opts.preferredPort + index).concat(0);
|
|
1934
|
+
for (const port of candidatePorts) {
|
|
1935
|
+
try {
|
|
1936
|
+
const server = Bun.serve({
|
|
1937
|
+
port,
|
|
1938
|
+
idleTimeout: 30,
|
|
1939
|
+
fetch: async (req) => {
|
|
1940
|
+
const url = new URL(req.url);
|
|
1941
|
+
if (url.pathname === "/") {
|
|
1942
|
+
return new Response(html, {
|
|
1943
|
+
headers: {
|
|
1944
|
+
"content-type": "text/html; charset=utf-8",
|
|
1945
|
+
"cache-control": "no-store"
|
|
1946
|
+
}
|
|
1947
|
+
});
|
|
1948
|
+
}
|
|
1949
|
+
if (url.pathname === "/healthz") {
|
|
1950
|
+
return Response.json({ ok: true, port, serverUrl: opts.serverUrl, sessionId: opts.sessionId });
|
|
1951
|
+
}
|
|
1952
|
+
if (url.pathname === "/api/status") {
|
|
1953
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/system/status");
|
|
1954
|
+
}
|
|
1955
|
+
if (url.pathname === "/api/requests") {
|
|
1956
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/requests?status=all&limit=20");
|
|
1957
|
+
}
|
|
1958
|
+
if (url.pathname === "/api/jobs") {
|
|
1959
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/jobs?status=all&limit=20");
|
|
1960
|
+
}
|
|
1961
|
+
if (url.pathname === "/api/completions") {
|
|
1962
|
+
return await proxyMonitoringHubRequest(opts.serverUrl, opts.authToken, "/completions?status=all&limit=20");
|
|
1963
|
+
}
|
|
1964
|
+
return new Response("Not found", { status: 404 });
|
|
1965
|
+
}
|
|
1966
|
+
});
|
|
1967
|
+
return {
|
|
1968
|
+
url: `http://127.0.0.1:${server.port}`,
|
|
1969
|
+
port: Number(server.port),
|
|
1970
|
+
embedded: true,
|
|
1971
|
+
stop: () => server.stop(true)
|
|
1972
|
+
};
|
|
1973
|
+
} catch {}
|
|
1321
1974
|
}
|
|
1322
|
-
return
|
|
1975
|
+
return null;
|
|
1976
|
+
}
|
|
1977
|
+
async function resolveMonitoringHub(opts) {
|
|
1978
|
+
const explicit = normalizeUrl(opts.preferredUrl);
|
|
1979
|
+
if (explicit) {
|
|
1980
|
+
if (await looksLikeMonitoringHub(explicit)) {
|
|
1981
|
+
return { url: explicit, port: 0, stop: () => {}, embedded: false };
|
|
1982
|
+
}
|
|
1983
|
+
console.warn(`[pushpals] Preferred monitoring hub ${explicit} is unavailable; starting embedded monitor instead.`);
|
|
1984
|
+
}
|
|
1985
|
+
for (let port = opts.fallbackPort;port < opts.fallbackPort + MONITOR_SCAN_PORTS; port++) {
|
|
1986
|
+
const candidate = `http://127.0.0.1:${port}`;
|
|
1987
|
+
if (await looksLikeMonitoringHub(candidate)) {
|
|
1988
|
+
return { url: candidate, port, stop: () => {}, embedded: false };
|
|
1989
|
+
}
|
|
1990
|
+
}
|
|
1991
|
+
const embedded = await startEmbeddedMonitoringHub(opts);
|
|
1992
|
+
if (!embedded) {
|
|
1993
|
+
console.warn("[pushpals] Embedded monitoring hub could not start on any expected local port.");
|
|
1994
|
+
}
|
|
1995
|
+
return embedded;
|
|
1323
1996
|
}
|
|
1324
1997
|
async function sendMessageToLocalBuddy(localAgentUrl, text) {
|
|
1325
1998
|
let response;
|
|
@@ -1493,8 +2166,7 @@ async function runSessionStream(serverUrl, sessionId, authToken, print, signal)
|
|
|
1493
2166
|
async function openMonitoringHub(url) {
|
|
1494
2167
|
let cmd = null;
|
|
1495
2168
|
if (process.platform === "win32") {
|
|
1496
|
-
|
|
1497
|
-
cmd = ["powershell", "-NoProfile", "-Command", `Start-Process '${escaped}'`];
|
|
2169
|
+
cmd = ["cmd", "/c", "start", "", url];
|
|
1498
2170
|
} else if (process.platform === "darwin") {
|
|
1499
2171
|
cmd = ["open", url];
|
|
1500
2172
|
} else {
|
|
@@ -1514,7 +2186,6 @@ async function main() {
|
|
|
1514
2186
|
const parsed = parseArgs(argv);
|
|
1515
2187
|
if (!parsed)
|
|
1516
2188
|
return;
|
|
1517
|
-
const config = loadPushPalsConfig();
|
|
1518
2189
|
const cwd = process.cwd();
|
|
1519
2190
|
const repoRoot = await resolveCurrentGitRepoRoot(cwd);
|
|
1520
2191
|
if (!repoRoot) {
|
|
@@ -1523,6 +2194,25 @@ async function main() {
|
|
|
1523
2194
|
console.error("[pushpals] Run from a repo directory, or initialize one with `git init`.");
|
|
1524
2195
|
process.exit(1);
|
|
1525
2196
|
}
|
|
2197
|
+
const preparedRuntime = await prepareCliRuntime({
|
|
2198
|
+
repoRoot,
|
|
2199
|
+
runtimeRoot: parsed.runtimeRoot,
|
|
2200
|
+
runtimeTag: parsed.runtimeTag
|
|
2201
|
+
});
|
|
2202
|
+
console.log("[pushpals] Running runtime preflight...");
|
|
2203
|
+
console.log(`[pushpals] runtimeRoot=${preparedRuntime.runtimeRoot}`);
|
|
2204
|
+
if (preparedRuntime.runtimeTag) {
|
|
2205
|
+
console.log(`[pushpals] runtimeTag=${preparedRuntime.runtimeTag}`);
|
|
2206
|
+
} else if (!preparedRuntime.preflightUsesEmbeddedRuntime) {
|
|
2207
|
+
console.log("[pushpals] runtimeTag=(deferred; using repo config for preflight)");
|
|
2208
|
+
} else {
|
|
2209
|
+
console.log("[pushpals] runtimeTag=(deferred until embedded auto-start is needed)");
|
|
2210
|
+
}
|
|
2211
|
+
emitCliRuntimePreflight(preparedRuntime.runtimePreflight);
|
|
2212
|
+
if (!preparedRuntime.runtimePreflight.ok) {
|
|
2213
|
+
process.exit(1);
|
|
2214
|
+
}
|
|
2215
|
+
const config = preparedRuntime.runtimePreflight.config;
|
|
1526
2216
|
const serverUrl = normalizeUrl(parsed.serverUrl ?? process.env.PUSHPALS_SERVER_URL, config.server.url);
|
|
1527
2217
|
const localAgentUrl = normalizeUrl(parsed.localAgentUrl ?? process.env.EXPO_PUBLIC_LOCAL_AGENT_URL, config.client.localAgentUrl);
|
|
1528
2218
|
const sessionId = String(parsed.sessionId ?? process.env.PUSHPALS_SESSION_ID ?? config.sessionId).trim();
|
|
@@ -1542,9 +2232,10 @@ async function main() {
|
|
|
1542
2232
|
serverUrl,
|
|
1543
2233
|
localAgentUrl,
|
|
1544
2234
|
sourceControlManagerPort: config.sourceControlManager.port,
|
|
2235
|
+
sourceControlManagerRemote: config.sourceControlManager.remote,
|
|
1545
2236
|
authToken,
|
|
1546
|
-
|
|
1547
|
-
|
|
2237
|
+
preparedRuntime,
|
|
2238
|
+
requestedRuntimeTag: parsed.runtimeTag
|
|
1548
2239
|
});
|
|
1549
2240
|
health = await probeLocalBuddy(localAgentUrl, authToken);
|
|
1550
2241
|
} catch (err) {
|
|
@@ -1561,7 +2252,7 @@ async function main() {
|
|
|
1561
2252
|
}
|
|
1562
2253
|
process.exit(1);
|
|
1563
2254
|
}
|
|
1564
|
-
const localBuddyRepo = health.repo ?
|
|
2255
|
+
const localBuddyRepo = health.repo ? resolve3(health.repo) : "";
|
|
1565
2256
|
if (!localBuddyRepo) {
|
|
1566
2257
|
stopAutoStartedServices();
|
|
1567
2258
|
console.error("[pushpals] LocalBuddy health response did not include repo path.");
|
|
@@ -1579,20 +2270,35 @@ async function main() {
|
|
|
1579
2270
|
if (sessionId && sessionId !== localBuddySessionId) {
|
|
1580
2271
|
console.warn(`[pushpals] Requested sessionId=${sessionId}, but LocalBuddy is currently attached to sessionId=${localBuddySessionId}.`);
|
|
1581
2272
|
}
|
|
1582
|
-
const statePath =
|
|
2273
|
+
const statePath = resolve3(repoRoot, ".git", "pushpals-cli-state.json");
|
|
1583
2274
|
const saved = readCliState(statePath);
|
|
1584
2275
|
const preferredHubUrl = normalizeUrl(parsed.monitoringHubUrl ?? process.env.PUSHPALS_MONITOR_URL ?? saved.monitoringHubUrl ?? "");
|
|
1585
2276
|
const monitorPort = parsePositiveInt(process.env.PUSHPALS_CLIENT_PORT, DEFAULT_MONITOR_PORT);
|
|
1586
|
-
const
|
|
2277
|
+
const monitoringHub = await resolveMonitoringHub({
|
|
2278
|
+
preferredUrl: preferredHubUrl,
|
|
2279
|
+
fallbackPort: monitorPort,
|
|
2280
|
+
serverUrl,
|
|
2281
|
+
localAgentUrl,
|
|
2282
|
+
sessionId: localBuddySessionId,
|
|
2283
|
+
authToken
|
|
2284
|
+
});
|
|
2285
|
+
const monitoringHubUrl = monitoringHub?.url ?? "";
|
|
1587
2286
|
writeCliState(statePath, {
|
|
1588
|
-
monitoringHubUrl,
|
|
2287
|
+
monitoringHubUrl: monitoringHubUrl || undefined,
|
|
1589
2288
|
serverUrl,
|
|
1590
2289
|
localAgentUrl,
|
|
1591
2290
|
sessionId: localBuddySessionId,
|
|
1592
2291
|
repoRoot
|
|
1593
2292
|
});
|
|
1594
2293
|
console.log("[pushpals] Connected.");
|
|
1595
|
-
|
|
2294
|
+
if (monitoringHubUrl) {
|
|
2295
|
+
console.log(`monitoringHubUrl=${monitoringHubUrl}`);
|
|
2296
|
+
if (monitoringHub?.embedded) {
|
|
2297
|
+
console.log("[pushpals] Embedded monitoring hub is running.");
|
|
2298
|
+
}
|
|
2299
|
+
} else {
|
|
2300
|
+
console.log("monitoringHubUrl=unavailable");
|
|
2301
|
+
}
|
|
1596
2302
|
console.log(`serverUrl=${serverUrl}`);
|
|
1597
2303
|
console.log(`localAgentUrl=${localAgentUrl}`);
|
|
1598
2304
|
console.log(`sessionId=${localBuddySessionId}`);
|
|
@@ -1622,10 +2328,14 @@ ${line}
|
|
|
1622
2328
|
streamAbort.abort();
|
|
1623
2329
|
if (rl)
|
|
1624
2330
|
rl.close();
|
|
2331
|
+
try {
|
|
2332
|
+
monitoringHub?.stop();
|
|
2333
|
+
} catch {}
|
|
1625
2334
|
stopAutoStartedServices();
|
|
1626
2335
|
};
|
|
1627
2336
|
process.once("SIGINT", requestStop);
|
|
1628
2337
|
process.once("SIGTERM", requestStop);
|
|
2338
|
+
process.once("exit", requestStop);
|
|
1629
2339
|
rl = createInterface({
|
|
1630
2340
|
input: process.stdin,
|
|
1631
2341
|
output: process.stdout,
|
|
@@ -1644,20 +2354,25 @@ ${line}
|
|
|
1644
2354
|
break;
|
|
1645
2355
|
}
|
|
1646
2356
|
if (text === "/hub") {
|
|
1647
|
-
console.log(`monitoringHubUrl=${monitoringHubUrl}`);
|
|
2357
|
+
console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
|
|
1648
2358
|
rl.prompt();
|
|
1649
2359
|
continue;
|
|
1650
2360
|
}
|
|
1651
2361
|
if (text === "/status") {
|
|
1652
2362
|
console.log(`serverUrl=${serverUrl}`);
|
|
1653
2363
|
console.log(`localAgentUrl=${localAgentUrl}`);
|
|
1654
|
-
console.log(`sessionId=${
|
|
2364
|
+
console.log(`sessionId=${localBuddySessionId}`);
|
|
1655
2365
|
console.log(`repoRoot=${repoRoot}`);
|
|
1656
|
-
console.log(`monitoringHubUrl=${monitoringHubUrl}`);
|
|
2366
|
+
console.log(monitoringHubUrl ? `monitoringHubUrl=${monitoringHubUrl}` : "monitoringHubUrl=unavailable");
|
|
1657
2367
|
rl.prompt();
|
|
1658
2368
|
continue;
|
|
1659
2369
|
}
|
|
1660
2370
|
if (text === "/open") {
|
|
2371
|
+
if (!monitoringHubUrl) {
|
|
2372
|
+
console.log("[pushpals] Monitoring hub is unavailable.");
|
|
2373
|
+
rl.prompt();
|
|
2374
|
+
continue;
|
|
2375
|
+
}
|
|
1661
2376
|
const opened = await openMonitoringHub(monitoringHubUrl);
|
|
1662
2377
|
console.log(opened ? `[pushpals] Opened ${monitoringHubUrl}` : `[pushpals] Failed to open browser. Use this link: ${monitoringHubUrl}`);
|
|
1663
2378
|
rl.prompt();
|
|
@@ -1672,7 +2387,16 @@ ${line}
|
|
|
1672
2387
|
requestStop();
|
|
1673
2388
|
await Promise.race([streamTask, Bun.sleep(2000)]);
|
|
1674
2389
|
}
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
2390
|
+
if (import.meta.main) {
|
|
2391
|
+
main().catch((err) => {
|
|
2392
|
+
console.error(`[pushpals] Fatal: ${String(err)}`);
|
|
2393
|
+
process.exit(1);
|
|
2394
|
+
});
|
|
2395
|
+
}
|
|
2396
|
+
export {
|
|
2397
|
+
startEmbeddedMonitoringHub,
|
|
2398
|
+
resolveBundledRuntimeAssetSource,
|
|
2399
|
+
prepareCliRuntime,
|
|
2400
|
+
buildEmbeddedRuntimeEnv,
|
|
2401
|
+
buildEmbeddedMonitoringHubHtml
|
|
2402
|
+
};
|