opencode-agenthub 0.1.0 → 0.1.2
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/README.md +5 -2
- package/dist/composer/builtin-assets.js +2 -0
- package/dist/composer/compose.js +30 -1
- package/dist/composer/library/bundles/auto.json +1 -1
- package/dist/composer/library/bundles/explore.json +21 -0
- package/dist/composer/library/instructions/hr-boundaries.md +1 -0
- package/dist/composer/library/instructions/hr-protocol.md +12 -13
- package/dist/composer/library/souls/explore.md +26 -0
- package/dist/composer/library/souls/hr-adapter.md +1 -0
- package/dist/composer/library/souls/hr-cto.md +3 -1
- package/dist/composer/library/souls/hr-planner.md +3 -3
- package/dist/composer/library/souls/hr-sourcer.md +1 -0
- package/dist/composer/library/souls/hr.md +24 -25
- package/dist/composer/model-utils.js +25 -1
- package/dist/composer/opencode-profile.js +366 -78
- package/dist/composer/platform.js +19 -0
- package/dist/composer/settings.js +103 -2
- package/dist/skills/agenthub-doctor/diagnose.js +21 -0
- package/dist/skills/agenthub-doctor/fix.js +1 -1
- package/dist/skills/agenthub-doctor/interactive.js +21 -2
- package/dist/skills/hr-assembly/SKILL.md +3 -0
- package/dist/skills/hr-final-check/SKILL.md +3 -1
- package/dist/skills/hr-staffing/SKILL.md +4 -4
- package/dist/skills/hr-support/bin/validate_staged_package.py +43 -11
- package/package.json +3 -3
|
@@ -37,19 +37,30 @@ import {
|
|
|
37
37
|
import { readPackageVersion } from "./package-version.js";
|
|
38
38
|
import {
|
|
39
39
|
mergeAgentHubSettingsDefaults,
|
|
40
|
-
|
|
41
|
-
|
|
40
|
+
hrAgentNames,
|
|
41
|
+
loadNativeOpenCodePreferences,
|
|
42
|
+
listAvailableOpencodeModels,
|
|
43
|
+
probeOpencodeModelAvailability,
|
|
44
|
+
readHrKnownModelIds,
|
|
42
45
|
recommendedHrBootstrapModel,
|
|
43
|
-
recommendedHrBootstrapVariant,
|
|
44
46
|
readAgentHubSettings,
|
|
47
|
+
resolveHrBootstrapAgentModels,
|
|
48
|
+
validateHrAgentModelConfiguration,
|
|
45
49
|
writeAgentHubSettings
|
|
46
50
|
} from "./settings.js";
|
|
51
|
+
import {
|
|
52
|
+
validateModelAgainstCatalog,
|
|
53
|
+
validateModelIdentifier
|
|
54
|
+
} from "./model-utils.js";
|
|
47
55
|
import {
|
|
48
56
|
displayHomeConfigPath,
|
|
57
|
+
interactivePromptResetSequence,
|
|
49
58
|
resolvePythonCommand,
|
|
50
59
|
shouldChmod,
|
|
51
60
|
shouldOfferEnvrc,
|
|
61
|
+
shouldUseReadlineTerminal,
|
|
52
62
|
spawnOptions,
|
|
63
|
+
stripTerminalControlInput,
|
|
53
64
|
windowsStartupNotice
|
|
54
65
|
} from "./platform.js";
|
|
55
66
|
const cliCommand = "agenthub";
|
|
@@ -144,8 +155,8 @@ FLAGS (hr)
|
|
|
144
155
|
hr <profile> Test an HR-home profile in the current workspace
|
|
145
156
|
hr last Reuse the last HR profile tested in this workspace
|
|
146
157
|
hr set <profile> Unsupported (use explicit '${cliCommand} hr <profile>' each time)
|
|
147
|
-
first bootstrap
|
|
148
|
-
|
|
158
|
+
first bootstrap HR first asks about your situation, inspects resources,
|
|
159
|
+
reports a recommendation, then lets you accept or override it
|
|
149
160
|
|
|
150
161
|
FLAGS (new / compose profile)
|
|
151
162
|
--from <profile> Seed bundles/plugins from an existing profile
|
|
@@ -709,6 +720,7 @@ const printRuntimeBanner = (label, root) => {
|
|
|
709
720
|
process.stdout.write(`[agenthub] Home: ${root}
|
|
710
721
|
`);
|
|
711
722
|
};
|
|
723
|
+
let suppressNextHrRuntimeBanner = false;
|
|
712
724
|
const listPromotablePackageIds = async (hrRoot = defaultHrHome()) => {
|
|
713
725
|
try {
|
|
714
726
|
const entries = await readdir(path.join(hrRoot, "staging"), { withFileTypes: true });
|
|
@@ -838,70 +850,285 @@ const ensureHomeReadyOrBootstrap = async (targetRoot = defaultAgentHubHome()) =>
|
|
|
838
850
|
process.stdout.write(`\u2713 First run \u2014 initialised coding system at ${targetRoot}
|
|
839
851
|
`);
|
|
840
852
|
};
|
|
841
|
-
const
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
853
|
+
const formatCountLabel = (count, singular, plural = `${singular}s`) => {
|
|
854
|
+
if (count === null) return `unknown ${plural}`;
|
|
855
|
+
return `${count} ${count === 1 ? singular : plural}`;
|
|
856
|
+
};
|
|
857
|
+
const inspectHrBootstrapResources = async (hrRoot) => {
|
|
858
|
+
const [configuredGithubSources, configuredModelCatalogSources, knownModels, availableModels, freeModels, native] = await Promise.all([
|
|
859
|
+
countConfiguredHrGithubSources(hrRoot),
|
|
860
|
+
countConfiguredHrModelCatalogSources(hrRoot),
|
|
861
|
+
readHrKnownModelIds(hrRoot),
|
|
862
|
+
listAvailableOpencodeModels(),
|
|
863
|
+
listOpencodeFreeModels(),
|
|
864
|
+
loadNativeOpenCodePreferences()
|
|
865
|
+
]);
|
|
866
|
+
const recommendedAvailability = await probeOpencodeModelAvailability(
|
|
867
|
+
recommendedHrBootstrapModel,
|
|
868
|
+
{ listModels: async () => availableModels }
|
|
869
|
+
);
|
|
870
|
+
return {
|
|
871
|
+
configuredGithubSources,
|
|
872
|
+
configuredModelCatalogSources,
|
|
873
|
+
knownModels,
|
|
874
|
+
availableModels,
|
|
875
|
+
freeModels,
|
|
876
|
+
nativeModel: native?.model,
|
|
877
|
+
recommendedAvailability
|
|
878
|
+
};
|
|
879
|
+
};
|
|
880
|
+
const recommendHrBootstrapSelection = (resources) => {
|
|
881
|
+
if (resources.recommendedAvailability.available) {
|
|
882
|
+
return {
|
|
883
|
+
strategy: "recommended",
|
|
884
|
+
summary: `I recommend starting with the recommended HR model (${recommendedHrBootstrapModel}).`,
|
|
885
|
+
reason: "It is available in this opencode environment and matches the built-in HR default."
|
|
886
|
+
};
|
|
887
|
+
}
|
|
888
|
+
if (resources.freeModels.length > 0) {
|
|
889
|
+
return {
|
|
890
|
+
strategy: "free",
|
|
891
|
+
summary: "I recommend starting with the best available free HR model.",
|
|
892
|
+
reason: `${resources.recommendedAvailability.message} A free fallback is available right now.`
|
|
893
|
+
};
|
|
894
|
+
}
|
|
895
|
+
const nativeModelSyntax = resources.nativeModel ? validateModelIdentifier(resources.nativeModel) : void 0;
|
|
896
|
+
if (resources.nativeModel && nativeModelSyntax?.ok) {
|
|
897
|
+
return {
|
|
898
|
+
strategy: "native",
|
|
899
|
+
summary: `I recommend reusing your native default model (${resources.nativeModel}).`,
|
|
900
|
+
reason: "No verified free fallback is visible, but your native opencode default looks usable."
|
|
901
|
+
};
|
|
902
|
+
}
|
|
903
|
+
return {
|
|
904
|
+
strategy: "custom",
|
|
905
|
+
summary: "I recommend entering a custom HR model now.",
|
|
906
|
+
reason: "The recommended preset is not currently verified and no safer automatic fallback was found."
|
|
907
|
+
};
|
|
908
|
+
};
|
|
909
|
+
const printHrBootstrapAssessment = (resources, recommendation) => {
|
|
910
|
+
void resources;
|
|
911
|
+
process.stdout.write(`
|
|
912
|
+
Recommended setup:
|
|
913
|
+
${recommendation.summary}
|
|
914
|
+
|
|
915
|
+
`);
|
|
916
|
+
};
|
|
917
|
+
const buildHrModelSelection = async (rl, hrRoot, strategy) => {
|
|
918
|
+
if (strategy === "recommended") {
|
|
846
919
|
process.stdout.write(
|
|
847
|
-
`
|
|
920
|
+
`[agenthub] Recommended HR preset requires OpenAI model access in your opencode environment.
|
|
848
921
|
`
|
|
849
922
|
);
|
|
850
|
-
|
|
851
|
-
|
|
923
|
+
return {
|
|
924
|
+
consoleModel: recommendedHrBootstrapModel,
|
|
925
|
+
subagentStrategy: "recommended",
|
|
926
|
+
sharedSubagentModel: recommendedHrBootstrapModel
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
if (strategy === "native") {
|
|
930
|
+
const native = await loadNativeOpenCodePreferences();
|
|
931
|
+
if (!native?.model) {
|
|
932
|
+
process.stdout.write("[agenthub] No native default model is configured. Choose another fallback.\n");
|
|
933
|
+
return buildHrModelSelection(rl, hrRoot, "free");
|
|
934
|
+
}
|
|
935
|
+
return {
|
|
936
|
+
consoleModel: native.model,
|
|
937
|
+
subagentStrategy: "native",
|
|
938
|
+
sharedSubagentModel: native.model
|
|
939
|
+
};
|
|
940
|
+
}
|
|
941
|
+
if (strategy === "free") {
|
|
942
|
+
const freeModels = await listOpencodeFreeModels();
|
|
943
|
+
const fallbackFreeModel = freeModels.includes("opencode/minimax-m2.5-free") ? "opencode/minimax-m2.5-free" : freeModels[0] || "opencode/minimax-m2.5-free";
|
|
944
|
+
const choices = freeModels.length > 0 ? freeModels : [fallbackFreeModel];
|
|
945
|
+
process.stdout.write("Current opencode free models:\n");
|
|
946
|
+
const selected = choices.length === 1 ? (process.stdout.write(` 1. ${choices[0]}
|
|
947
|
+
`), choices[0]) : await promptIndexedChoice(
|
|
948
|
+
rl,
|
|
949
|
+
"Choose a free model for HR",
|
|
950
|
+
choices,
|
|
951
|
+
fallbackFreeModel
|
|
852
952
|
);
|
|
853
|
-
|
|
854
|
-
|
|
953
|
+
return {
|
|
954
|
+
consoleModel: selected,
|
|
955
|
+
subagentStrategy: "free",
|
|
956
|
+
sharedSubagentModel: selected
|
|
957
|
+
};
|
|
958
|
+
}
|
|
959
|
+
const custom = await promptRequired(rl, "Custom HR model", recommendedHrBootstrapModel);
|
|
960
|
+
return {
|
|
961
|
+
consoleModel: custom,
|
|
962
|
+
subagentStrategy: "custom",
|
|
963
|
+
sharedSubagentModel: custom
|
|
964
|
+
};
|
|
965
|
+
};
|
|
966
|
+
const checkHrBootstrapSelection = async (hrRoot, selection) => {
|
|
967
|
+
const model = selection.sharedSubagentModel || selection.consoleModel;
|
|
968
|
+
if (!model) {
|
|
969
|
+
return {
|
|
970
|
+
ok: false,
|
|
971
|
+
selection,
|
|
972
|
+
stage: "syntax",
|
|
973
|
+
message: "Model id cannot be blank."
|
|
974
|
+
};
|
|
975
|
+
}
|
|
976
|
+
const syntax = validateModelIdentifier(model);
|
|
977
|
+
if (!syntax.ok) {
|
|
978
|
+
return { ok: false, selection, stage: "syntax", message: syntax.message };
|
|
979
|
+
}
|
|
980
|
+
const knownModels = await readHrKnownModelIds(hrRoot);
|
|
981
|
+
const catalog = validateModelAgainstCatalog(model, knownModels);
|
|
982
|
+
if (!catalog.ok) {
|
|
983
|
+
return { ok: false, selection, stage: "catalog", message: catalog.message };
|
|
984
|
+
}
|
|
985
|
+
const availability = await probeOpencodeModelAvailability(model, {
|
|
986
|
+
listModels: listAvailableOpencodeModels
|
|
987
|
+
});
|
|
988
|
+
if (!availability.available) {
|
|
989
|
+
return {
|
|
990
|
+
ok: false,
|
|
991
|
+
selection,
|
|
992
|
+
stage: availability.reason === "probe_failed" ? "probe_failed" : "availability",
|
|
993
|
+
message: availability.message
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
return { ok: true, selection };
|
|
997
|
+
};
|
|
998
|
+
const promptValidatedHrModelSelection = async (rl, hrRoot, strategy) => {
|
|
999
|
+
let selection = await buildHrModelSelection(rl, hrRoot, strategy);
|
|
1000
|
+
while (true) {
|
|
1001
|
+
const check = await checkHrBootstrapSelection(hrRoot, selection);
|
|
1002
|
+
if (check.ok) return check.selection;
|
|
1003
|
+
process.stdout.write(`${check.message}
|
|
1004
|
+
`);
|
|
1005
|
+
if (check.stage === "syntax" && selection.subagentStrategy === "custom") {
|
|
1006
|
+
selection = await buildHrModelSelection(rl, hrRoot, "custom");
|
|
1007
|
+
continue;
|
|
1008
|
+
}
|
|
1009
|
+
const action = await promptChoice(
|
|
855
1010
|
rl,
|
|
856
|
-
"
|
|
857
|
-
["
|
|
858
|
-
"
|
|
1011
|
+
check.stage === "probe_failed" ? "Model verification failed \u2014 continue or choose a fallback" : "Choose a fallback",
|
|
1012
|
+
["continue", "free", "native", "custom", "retry recommended"],
|
|
1013
|
+
check.stage === "probe_failed" ? "continue" : "free"
|
|
859
1014
|
);
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
} else if (subagentStrategy === "custom") {
|
|
878
|
-
sharedSubagentModel = await promptRequired(
|
|
1015
|
+
if (action === "continue") return selection;
|
|
1016
|
+
selection = await buildHrModelSelection(
|
|
1017
|
+
rl,
|
|
1018
|
+
hrRoot,
|
|
1019
|
+
action === "retry recommended" ? "recommended" : action
|
|
1020
|
+
);
|
|
1021
|
+
}
|
|
1022
|
+
};
|
|
1023
|
+
const promptHrBootstrapModelSelection = async (hrRoot) => {
|
|
1024
|
+
const rl = createPromptInterface();
|
|
1025
|
+
try {
|
|
1026
|
+
process.stdout.write("\nFirst-time HR Office setup\n");
|
|
1027
|
+
const resources = await inspectHrBootstrapResources(hrRoot);
|
|
1028
|
+
const recommendation = recommendHrBootstrapSelection(resources);
|
|
1029
|
+
printHrBootstrapAssessment(resources, recommendation);
|
|
1030
|
+
while (true) {
|
|
1031
|
+
const action = await promptChoice(
|
|
879
1032
|
rl,
|
|
880
|
-
"
|
|
881
|
-
|
|
1033
|
+
"Apply this recommendation now",
|
|
1034
|
+
["accept", "recommended", "free", "native", "custom"],
|
|
1035
|
+
"accept"
|
|
882
1036
|
);
|
|
883
|
-
|
|
1037
|
+
const strategy = action === "accept" ? recommendation.strategy : action;
|
|
1038
|
+
const validated = await promptValidatedHrModelSelection(rl, hrRoot, strategy);
|
|
1039
|
+
const finalModel = validated.sharedSubagentModel || validated.consoleModel;
|
|
1040
|
+
if (!finalModel) continue;
|
|
1041
|
+
const finalSyntax = validateModelIdentifier(finalModel);
|
|
1042
|
+
if (finalSyntax.ok) {
|
|
1043
|
+
return validated;
|
|
1044
|
+
}
|
|
884
1045
|
}
|
|
885
|
-
|
|
886
|
-
|
|
1046
|
+
} finally {
|
|
1047
|
+
rl.close();
|
|
1048
|
+
}
|
|
1049
|
+
};
|
|
1050
|
+
const shouldUseInteractivePrompts = () => process.env.OPENCODE_AGENTHUB_FORCE_INTERACTIVE_PROMPTS === "1" || Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1051
|
+
const applyHrModelSelection = async (targetRoot, selection) => {
|
|
1052
|
+
await installHrOfficeHomeWithOptions({
|
|
1053
|
+
hrRoot: targetRoot,
|
|
1054
|
+
hrModelSelection: selection
|
|
1055
|
+
});
|
|
1056
|
+
};
|
|
1057
|
+
const repairHrModelConfigurationIfNeeded = async (targetRoot) => {
|
|
1058
|
+
const settings = await readAgentHubSettings(targetRoot);
|
|
1059
|
+
if (!shouldUseInteractivePrompts()) {
|
|
1060
|
+
for (const agentName of hrAgentNames) {
|
|
1061
|
+
const model = settings?.agents?.[agentName]?.model;
|
|
1062
|
+
if (typeof model !== "string" || model.trim().length === 0) continue;
|
|
1063
|
+
const syntax = validateModelIdentifier(model);
|
|
1064
|
+
if (!syntax.ok) {
|
|
1065
|
+
fail(`HR model configuration needs attention. Agent '${agentName}' model '${model}' is invalid: ${syntax.message}`);
|
|
1066
|
+
}
|
|
1067
|
+
}
|
|
1068
|
+
return;
|
|
1069
|
+
}
|
|
1070
|
+
const status = await validateHrAgentModelConfiguration(targetRoot, settings);
|
|
1071
|
+
if (status.valid) return;
|
|
1072
|
+
const rl = createPromptInterface();
|
|
1073
|
+
try {
|
|
1074
|
+
process.stdout.write("[agenthub] HR model configuration needs attention.\n");
|
|
1075
|
+
if (status.message) process.stdout.write(`${status.message}
|
|
887
1076
|
`);
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
1077
|
+
const repair = await promptBoolean(
|
|
1078
|
+
rl,
|
|
1079
|
+
"Reconfigure HR models now?",
|
|
1080
|
+
true
|
|
1081
|
+
);
|
|
1082
|
+
if (!repair) {
|
|
1083
|
+
fail("Aborted before repairing invalid HR model configuration.");
|
|
1084
|
+
}
|
|
1085
|
+
const fallback = await promptChoice(
|
|
1086
|
+
rl,
|
|
1087
|
+
"Choose a fallback",
|
|
1088
|
+
["free", "native", "custom", "retry recommended"],
|
|
1089
|
+
"free"
|
|
1090
|
+
);
|
|
1091
|
+
const validated = await promptValidatedHrModelSelection(
|
|
1092
|
+
rl,
|
|
1093
|
+
targetRoot,
|
|
1094
|
+
fallback === "retry recommended" ? "recommended" : fallback
|
|
1095
|
+
);
|
|
1096
|
+
const resolved = await resolveHrBootstrapAgentModels({
|
|
1097
|
+
targetRoot,
|
|
1098
|
+
selection: validated
|
|
1099
|
+
});
|
|
1100
|
+
const merged = mergeAgentHubSettingsDefaults(settings || {});
|
|
1101
|
+
merged.agents = merged.agents || {};
|
|
1102
|
+
for (const agentName of hrAgentNames) {
|
|
1103
|
+
const resolvedSelection = resolved.agentModels[agentName];
|
|
1104
|
+
merged.agents[agentName] = {
|
|
1105
|
+
...merged.agents[agentName] || {},
|
|
1106
|
+
model: resolvedSelection.model,
|
|
1107
|
+
...resolvedSelection.variant ? { variant: resolvedSelection.variant } : {}
|
|
1108
|
+
};
|
|
1109
|
+
if (!resolvedSelection.variant) delete merged.agents[agentName].variant;
|
|
1110
|
+
}
|
|
1111
|
+
merged.meta = {
|
|
1112
|
+
...merged.meta,
|
|
1113
|
+
onboarding: {
|
|
1114
|
+
...merged.meta?.onboarding,
|
|
1115
|
+
modelStrategy: resolved.strategy,
|
|
1116
|
+
mode: merged.meta?.onboarding?.mode || "hr-office",
|
|
1117
|
+
importedNativeBasics: merged.meta?.onboarding?.importedNativeBasics ?? true,
|
|
1118
|
+
importedNativeAgents: merged.meta?.onboarding?.importedNativeAgents ?? true,
|
|
1119
|
+
createdAt: merged.meta?.onboarding?.createdAt || (/* @__PURE__ */ new Date()).toISOString()
|
|
1120
|
+
}
|
|
892
1121
|
};
|
|
1122
|
+
await writeAgentHubSettings(targetRoot, merged);
|
|
1123
|
+
process.stdout.write("[agenthub] Updated HR model configuration.\n");
|
|
893
1124
|
} finally {
|
|
894
1125
|
rl.close();
|
|
895
1126
|
}
|
|
896
1127
|
};
|
|
897
1128
|
const printHrModelOverrideHint = (targetRoot) => {
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
`[agenthub] Change later with: ${cliCommand} doctor --target-root ${targetRoot} --agent ${hrPrimaryAgentName} --model <model>`,
|
|
902
|
-
`[agenthub] HR subagents: ${hrSubagentNames.join(", ")} (use the same doctor command with --agent <name>)`
|
|
903
|
-
].join("\n") + "\n"
|
|
904
|
-
);
|
|
1129
|
+
void targetRoot;
|
|
1130
|
+
process.stdout.write(`Tip: change HR models later with '${cliCommand} doctor'.
|
|
1131
|
+
`);
|
|
905
1132
|
};
|
|
906
1133
|
const countConfiguredHrGithubSources = async (targetRoot) => {
|
|
907
1134
|
try {
|
|
@@ -952,8 +1179,12 @@ const syncHrSourceInventoryOnFirstRun = async (targetRoot) => {
|
|
|
952
1179
|
);
|
|
953
1180
|
}
|
|
954
1181
|
const sourceLabel = sourceParts.length > 0 ? sourceParts.join(" + ") : "configured HR sources";
|
|
955
|
-
process.stdout.write(
|
|
956
|
-
`
|
|
1182
|
+
process.stdout.write(
|
|
1183
|
+
`
|
|
1184
|
+
Step 3/3 \xB7 Sync inventory
|
|
1185
|
+
Sync the HR sourcer inventory from ${sourceLabel} \u2014 this may take a moment, please wait...
|
|
1186
|
+
`
|
|
1187
|
+
);
|
|
957
1188
|
try {
|
|
958
1189
|
const pythonCommand = resolvePythonCommand();
|
|
959
1190
|
const scriptPath = path.join(targetRoot, "bin", "sync_sources.py");
|
|
@@ -980,12 +1211,10 @@ const syncHrSourceInventoryOnFirstRun = async (targetRoot) => {
|
|
|
980
1211
|
});
|
|
981
1212
|
const summary = stdout.trim();
|
|
982
1213
|
if (code === 0) {
|
|
983
|
-
|
|
1214
|
+
void summary;
|
|
1215
|
+
const repoSummary = configuredSourceCount && configuredSourceCount > 0 ? `${configuredSourceCount} repo${configuredSourceCount === 1 ? "" : "s"}` : "configured sources";
|
|
1216
|
+
process.stdout.write(`\u2713 HR sourcer inventory sync complete (${repoSummary}).
|
|
984
1217
|
`);
|
|
985
|
-
process.stdout.write(
|
|
986
|
-
`[agenthub] HR source status: ${path.join(targetRoot, "source-status.json")}
|
|
987
|
-
`
|
|
988
|
-
);
|
|
989
1218
|
return;
|
|
990
1219
|
}
|
|
991
1220
|
process.stderr.write(
|
|
@@ -1005,18 +1234,33 @@ const syncHrSourceInventoryOnFirstRun = async (targetRoot) => {
|
|
|
1005
1234
|
};
|
|
1006
1235
|
const ensureHrOfficeReadyOrBootstrap = async (targetRoot = defaultHrHome(), options = {}) => {
|
|
1007
1236
|
if (await hrHomeInitialized(targetRoot)) return;
|
|
1008
|
-
const shouldPrompt =
|
|
1237
|
+
const shouldPrompt = shouldUseInteractivePrompts();
|
|
1238
|
+
process.stdout.write("\nHR Office \u2014 first-time setup\n\n");
|
|
1239
|
+
process.stdout.write(
|
|
1240
|
+
"Heads up: a full HR assemble can take about 20\u201330 minutes because AI may need time to choose and evaluate the souls and skills your agents need.\n\n"
|
|
1241
|
+
);
|
|
1242
|
+
process.stdout.write("This will:\n");
|
|
1243
|
+
process.stdout.write("1. Choose an AI model for HR agents\n");
|
|
1244
|
+
process.stdout.write("2. Create the HR Office workspace\n");
|
|
1245
|
+
if (options.syncSourcesOnFirstRun ?? true) {
|
|
1246
|
+
process.stdout.write("3. Sync the HR sourcer inventory (this may take a little longer)\n\n");
|
|
1247
|
+
} else {
|
|
1248
|
+
process.stdout.write("3. Skip inventory sync for now because you are assembling only\n\n");
|
|
1249
|
+
}
|
|
1009
1250
|
const hrModelSelection = shouldPrompt ? await promptHrBootstrapModelSelection(targetRoot) : void 0;
|
|
1010
|
-
await
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
process.stdout.write(`\u2713 First run \u2014 initialised HR Office at ${targetRoot}
|
|
1251
|
+
await applyHrModelSelection(targetRoot, hrModelSelection || {});
|
|
1252
|
+
process.stdout.write(`
|
|
1253
|
+
Step 2/3 \xB7 Create workspace
|
|
1254
|
+
\u2713 First run \u2014 initialised HR Office at ${targetRoot}
|
|
1015
1255
|
`);
|
|
1016
1256
|
printHrModelOverrideHint(targetRoot);
|
|
1017
1257
|
if (options.syncSourcesOnFirstRun ?? true) {
|
|
1018
1258
|
await syncHrSourceInventoryOnFirstRun(targetRoot);
|
|
1019
1259
|
}
|
|
1260
|
+
process.stdout.write(`
|
|
1261
|
+
\u2713 HR Office is ready.
|
|
1262
|
+
`);
|
|
1263
|
+
suppressNextHrRuntimeBanner = true;
|
|
1020
1264
|
};
|
|
1021
1265
|
const isHrRuntimeSelection = (selection) => selection?.kind === "profile" && selection.profile === "hr";
|
|
1022
1266
|
const normalizeCsv = (value) => value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
@@ -1186,10 +1430,44 @@ const maybeConfigureEnvrc = async (workspace, configRoot) => {
|
|
|
1186
1430
|
rl.close();
|
|
1187
1431
|
}
|
|
1188
1432
|
};
|
|
1189
|
-
const createPromptInterface = () =>
|
|
1433
|
+
const createPromptInterface = () => {
|
|
1434
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1435
|
+
const terminal = interactive && shouldUseReadlineTerminal();
|
|
1436
|
+
if (terminal) {
|
|
1437
|
+
const resetSequence = interactivePromptResetSequence();
|
|
1438
|
+
if (resetSequence) process.stdout.write(resetSequence);
|
|
1439
|
+
}
|
|
1440
|
+
return readline.createInterface({
|
|
1441
|
+
input: process.stdin,
|
|
1442
|
+
output: process.stdout,
|
|
1443
|
+
terminal
|
|
1444
|
+
});
|
|
1445
|
+
};
|
|
1446
|
+
const scriptedPromptAnswers = (() => {
|
|
1447
|
+
const raw = process.env.OPENCODE_AGENTHUB_SCRIPTED_ANSWERS;
|
|
1448
|
+
if (!raw) return void 0;
|
|
1449
|
+
try {
|
|
1450
|
+
const parsed2 = JSON.parse(raw);
|
|
1451
|
+
return Array.isArray(parsed2) ? parsed2.map((value) => typeof value === "string" ? value : String(value)) : void 0;
|
|
1452
|
+
} catch {
|
|
1453
|
+
return raw.split("\n");
|
|
1454
|
+
}
|
|
1455
|
+
})();
|
|
1456
|
+
let scriptedPromptIndex = 0;
|
|
1457
|
+
const askPrompt = async (rl, question) => {
|
|
1458
|
+
if (scriptedPromptAnswers && scriptedPromptIndex < scriptedPromptAnswers.length) {
|
|
1459
|
+
const answer = scriptedPromptAnswers[scriptedPromptIndex++] || "";
|
|
1460
|
+
const sanitized = stripTerminalControlInput(answer);
|
|
1461
|
+
process.stdout.write(`${question}${sanitized}
|
|
1462
|
+
`);
|
|
1463
|
+
return sanitized;
|
|
1464
|
+
}
|
|
1465
|
+
return stripTerminalControlInput(await rl.question(question));
|
|
1466
|
+
};
|
|
1190
1467
|
const promptRequired = async (rl, question, defaultValue) => {
|
|
1191
1468
|
while (true) {
|
|
1192
|
-
const answer = await
|
|
1469
|
+
const answer = await askPrompt(
|
|
1470
|
+
rl,
|
|
1193
1471
|
defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `
|
|
1194
1472
|
);
|
|
1195
1473
|
const value = normalizeOptional(answer) || defaultValue;
|
|
@@ -1198,14 +1476,16 @@ const promptRequired = async (rl, question, defaultValue) => {
|
|
|
1198
1476
|
}
|
|
1199
1477
|
};
|
|
1200
1478
|
const promptOptional = async (rl, question, defaultValue) => {
|
|
1201
|
-
const answer = await
|
|
1479
|
+
const answer = await askPrompt(
|
|
1480
|
+
rl,
|
|
1202
1481
|
defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `
|
|
1203
1482
|
);
|
|
1204
1483
|
return normalizeOptional(answer) || defaultValue;
|
|
1205
1484
|
};
|
|
1206
1485
|
const promptCsv = async (rl, question, defaultValues = []) => {
|
|
1207
1486
|
const defaultValue = defaultValues.join(", ");
|
|
1208
|
-
const answer = await
|
|
1487
|
+
const answer = await askPrompt(
|
|
1488
|
+
rl,
|
|
1209
1489
|
defaultValue ? `${question} [${defaultValue}]: ` : `${question}: `
|
|
1210
1490
|
);
|
|
1211
1491
|
return normalizeCsv(answer || defaultValue);
|
|
@@ -1213,7 +1493,7 @@ const promptCsv = async (rl, question, defaultValues = []) => {
|
|
|
1213
1493
|
const promptBoolean = async (rl, question, defaultValue) => {
|
|
1214
1494
|
const suffix = defaultValue ? "[Y/n]" : "[y/N]";
|
|
1215
1495
|
while (true) {
|
|
1216
|
-
const answer = (await rl
|
|
1496
|
+
const answer = (await askPrompt(rl, `${question} ${suffix}: `)).trim().toLowerCase();
|
|
1217
1497
|
if (!answer) return defaultValue;
|
|
1218
1498
|
if (answer === "y" || answer === "yes") return true;
|
|
1219
1499
|
if (answer === "n" || answer === "no") return false;
|
|
@@ -1223,7 +1503,7 @@ const promptBoolean = async (rl, question, defaultValue) => {
|
|
|
1223
1503
|
const promptChoice = async (rl, question, choices, defaultValue) => {
|
|
1224
1504
|
const label = `${question} [${choices.join("/")}] (${defaultValue})`;
|
|
1225
1505
|
while (true) {
|
|
1226
|
-
const answer = (await rl
|
|
1506
|
+
const answer = (await askPrompt(rl, `${label}: `)).trim().toLowerCase();
|
|
1227
1507
|
if (!answer) return defaultValue;
|
|
1228
1508
|
const match = choices.find((choice) => choice === answer);
|
|
1229
1509
|
if (match) return match;
|
|
@@ -1238,7 +1518,10 @@ const promptIndexedChoice = async (rl, question, choices, defaultValue) => {
|
|
|
1238
1518
|
});
|
|
1239
1519
|
const defaultIndex = Math.max(choices.indexOf(defaultValue), 0) + 1;
|
|
1240
1520
|
while (true) {
|
|
1241
|
-
const answer = (await
|
|
1521
|
+
const answer = (await askPrompt(
|
|
1522
|
+
rl,
|
|
1523
|
+
`${question} [1-${choices.length}] (${defaultIndex}): `
|
|
1524
|
+
)).trim().toLowerCase();
|
|
1242
1525
|
if (!answer) return defaultValue;
|
|
1243
1526
|
const numeric = Number(answer);
|
|
1244
1527
|
if (Number.isInteger(numeric) && numeric >= 1 && numeric <= choices.length) {
|
|
@@ -1522,7 +1805,8 @@ const createSkillDefinition = async (root, name, reservedOk = false) => {
|
|
|
1522
1805
|
};
|
|
1523
1806
|
const readProfileDefinition = async (root, name) => readJsonIfExists(path.join(root, "profiles", `${name}.json`));
|
|
1524
1807
|
const promptRecord = async (rl, question) => {
|
|
1525
|
-
const answer = await
|
|
1808
|
+
const answer = await askPrompt(
|
|
1809
|
+
rl,
|
|
1526
1810
|
`${question} (comma-separated key=value, blank to skip): `
|
|
1527
1811
|
);
|
|
1528
1812
|
const entries = normalizeCsv(answer);
|
|
@@ -2239,6 +2523,7 @@ if (parsed.command === "hr") {
|
|
|
2239
2523
|
await ensureHrOfficeReadyOrBootstrap(resolveSelectedHomeRoot(parsed), {
|
|
2240
2524
|
syncSourcesOnFirstRun: !parsed.assembleOnly
|
|
2241
2525
|
});
|
|
2526
|
+
await repairHrModelConfigurationIfNeeded(resolveSelectedHomeRoot(parsed) || defaultHrHome());
|
|
2242
2527
|
if (parsed.hrIntent?.kind === "office") {
|
|
2243
2528
|
parsed.workspace = resolveSelectedHomeRoot(parsed) || defaultHrHome();
|
|
2244
2529
|
} else if (parsed.hrIntent?.kind === "compose") {
|
|
@@ -2263,10 +2548,13 @@ if (parsed.command === "run" || parsed.command === "start" || parsed.command ===
|
|
|
2263
2548
|
}
|
|
2264
2549
|
const finalConfigRoot = resolveConfigRoot(parsed);
|
|
2265
2550
|
const result = await composeSelection(parsed, finalConfigRoot);
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
)
|
|
2551
|
+
if (!(parsed.command === "hr" && suppressNextHrRuntimeBanner)) {
|
|
2552
|
+
printRuntimeBanner(
|
|
2553
|
+
parsed.command === "hr" ? "HR Office" : "My Team",
|
|
2554
|
+
resolveSelectedHomeRoot(parsed) || defaultAgentHubHome()
|
|
2555
|
+
);
|
|
2556
|
+
}
|
|
2557
|
+
suppressNextHrRuntimeBanner = false;
|
|
2270
2558
|
if (shouldChmod()) {
|
|
2271
2559
|
await chmod(path.join(result.configRoot, "run.sh"), 493);
|
|
2272
2560
|
}
|
|
@@ -5,6 +5,22 @@ const shouldChmod = (win) => !isWindows(win);
|
|
|
5
5
|
const shouldOfferEnvrc = (win) => !isWindows(win);
|
|
6
6
|
const resolvePythonCommand = (win) => isWindows(win) ? "python" : "python3";
|
|
7
7
|
const spawnOptions = (win) => isWindows(win) ? { shell: true } : {};
|
|
8
|
+
const csiSequencePattern = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
9
|
+
const oscSequencePattern = /\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g;
|
|
10
|
+
const singleEscapePattern = /\u001b[@-_]/g;
|
|
11
|
+
const controlCharacterPattern = /[\u0000-\u001f\u007f]/g;
|
|
12
|
+
const degradedLeadingCsiFragmentPattern = /^(?:(?:\d{1,3};){1,10}\d{1,3}m)+/;
|
|
13
|
+
const stripTerminalControlInput = (value) => value.replace(oscSequencePattern, "").replace(csiSequencePattern, "").replace(singleEscapePattern, "").replace(controlCharacterPattern, "").replace(degradedLeadingCsiFragmentPattern, "");
|
|
14
|
+
const interactivePromptResetSequence = (win = detectWindows()) => isWindows(win) ? [
|
|
15
|
+
"\x1B[?1000l",
|
|
16
|
+
"\x1B[?1001l",
|
|
17
|
+
"\x1B[?1002l",
|
|
18
|
+
"\x1B[?1003l",
|
|
19
|
+
"\x1B[?1005l",
|
|
20
|
+
"\x1B[?1006l",
|
|
21
|
+
"\x1B[?1015l"
|
|
22
|
+
].join("") : "";
|
|
23
|
+
const shouldUseReadlineTerminal = (win = detectWindows()) => !isWindows(win);
|
|
8
24
|
const generateRunScript = () => `#!/usr/bin/env bash
|
|
9
25
|
set -euo pipefail
|
|
10
26
|
|
|
@@ -37,12 +53,15 @@ export {
|
|
|
37
53
|
displayHomeConfigPath,
|
|
38
54
|
generateRunCmd,
|
|
39
55
|
generateRunScript,
|
|
56
|
+
interactivePromptResetSequence,
|
|
40
57
|
isWindows,
|
|
41
58
|
resolveHomeConfigRoot,
|
|
42
59
|
resolvePythonCommand,
|
|
43
60
|
shouldChmod,
|
|
44
61
|
shouldOfferEnvrc,
|
|
62
|
+
shouldUseReadlineTerminal,
|
|
45
63
|
spawnOptions,
|
|
64
|
+
stripTerminalControlInput,
|
|
46
65
|
symlinkType,
|
|
47
66
|
windowsStartupNotice
|
|
48
67
|
};
|