opencode-agenthub 0.1.1 → 0.1.3
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 +2 -2
- package/dist/composer/bootstrap.js +2 -1
- package/dist/composer/builtin-assets.js +2 -0
- package/dist/composer/compose.js +30 -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 +13 -14
- package/dist/composer/library/souls/explore.md +26 -0
- package/dist/composer/library/souls/hr-adapter.md +3 -2
- package/dist/composer/library/souls/hr-cto.md +6 -3
- 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-verifier.md +2 -2
- package/dist/composer/library/souls/hr.md +25 -27
- package/dist/composer/model-utils.js +25 -1
- package/dist/composer/opencode-profile.js +340 -73
- package/dist/composer/platform.js +4 -1
- package/dist/composer/settings.js +103 -2
- package/dist/skills/agenthub-doctor/diagnose.js +21 -0
- package/dist/skills/agenthub-doctor/interactive.js +19 -0
- package/dist/skills/hr-assembly/SKILL.md +4 -1
- package/dist/skills/hr-final-check/SKILL.md +8 -6
- package/dist/skills/hr-staffing/SKILL.md +8 -5
- package/dist/skills/hr-support/bin/validate_staged_package.py +15 -1
- package/package.json +1 -1
|
@@ -37,19 +37,28 @@ 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,
|
|
49
57
|
interactivePromptResetSequence,
|
|
50
58
|
resolvePythonCommand,
|
|
51
59
|
shouldChmod,
|
|
52
60
|
shouldOfferEnvrc,
|
|
61
|
+
shouldUseReadlineTerminal,
|
|
53
62
|
spawnOptions,
|
|
54
63
|
stripTerminalControlInput,
|
|
55
64
|
windowsStartupNotice
|
|
@@ -146,8 +155,8 @@ FLAGS (hr)
|
|
|
146
155
|
hr <profile> Test an HR-home profile in the current workspace
|
|
147
156
|
hr last Reuse the last HR profile tested in this workspace
|
|
148
157
|
hr set <profile> Unsupported (use explicit '${cliCommand} hr <profile>' each time)
|
|
149
|
-
first bootstrap
|
|
150
|
-
|
|
158
|
+
first bootstrap HR first asks about your situation, inspects resources,
|
|
159
|
+
reports a recommendation, then lets you accept or override it
|
|
151
160
|
|
|
152
161
|
FLAGS (new / compose profile)
|
|
153
162
|
--from <profile> Seed bundles/plugins from an existing profile
|
|
@@ -711,6 +720,7 @@ const printRuntimeBanner = (label, root) => {
|
|
|
711
720
|
process.stdout.write(`[agenthub] Home: ${root}
|
|
712
721
|
`);
|
|
713
722
|
};
|
|
723
|
+
let suppressNextHrRuntimeBanner = false;
|
|
714
724
|
const listPromotablePackageIds = async (hrRoot = defaultHrHome()) => {
|
|
715
725
|
try {
|
|
716
726
|
const entries = await readdir(path.join(hrRoot, "staging"), { withFileTypes: true });
|
|
@@ -840,70 +850,285 @@ const ensureHomeReadyOrBootstrap = async (targetRoot = defaultAgentHubHome()) =>
|
|
|
840
850
|
process.stdout.write(`\u2713 First run \u2014 initialised coding system at ${targetRoot}
|
|
841
851
|
`);
|
|
842
852
|
};
|
|
843
|
-
const
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
847
|
-
|
|
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") {
|
|
848
919
|
process.stdout.write(
|
|
849
|
-
`
|
|
920
|
+
`[agenthub] Recommended HR preset requires OpenAI model access in your opencode environment.
|
|
850
921
|
`
|
|
851
922
|
);
|
|
852
|
-
|
|
853
|
-
|
|
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
|
|
854
952
|
);
|
|
855
|
-
|
|
856
|
-
|
|
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(
|
|
857
1010
|
rl,
|
|
858
|
-
"
|
|
859
|
-
["
|
|
860
|
-
"
|
|
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"
|
|
861
1014
|
);
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
} else if (subagentStrategy === "custom") {
|
|
880
|
-
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(
|
|
881
1032
|
rl,
|
|
882
|
-
"
|
|
883
|
-
|
|
1033
|
+
"Apply this recommendation now",
|
|
1034
|
+
["accept", "recommended", "free", "native", "custom"],
|
|
1035
|
+
"accept"
|
|
884
1036
|
);
|
|
885
|
-
|
|
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
|
+
}
|
|
886
1045
|
}
|
|
887
|
-
|
|
888
|
-
|
|
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}
|
|
889
1076
|
`);
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
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
|
+
}
|
|
894
1121
|
};
|
|
1122
|
+
await writeAgentHubSettings(targetRoot, merged);
|
|
1123
|
+
process.stdout.write("[agenthub] Updated HR model configuration.\n");
|
|
895
1124
|
} finally {
|
|
896
1125
|
rl.close();
|
|
897
1126
|
}
|
|
898
1127
|
};
|
|
899
1128
|
const printHrModelOverrideHint = (targetRoot) => {
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
`[agenthub] Change later with: ${cliCommand} doctor --target-root ${targetRoot} --agent ${hrPrimaryAgentName} --model <model>`,
|
|
904
|
-
`[agenthub] HR subagents: ${hrSubagentNames.join(", ")} (use the same doctor command with --agent <name>)`
|
|
905
|
-
].join("\n") + "\n"
|
|
906
|
-
);
|
|
1129
|
+
void targetRoot;
|
|
1130
|
+
process.stdout.write(`Tip: change HR models later with '${cliCommand} doctor'.
|
|
1131
|
+
`);
|
|
907
1132
|
};
|
|
908
1133
|
const countConfiguredHrGithubSources = async (targetRoot) => {
|
|
909
1134
|
try {
|
|
@@ -954,8 +1179,12 @@ const syncHrSourceInventoryOnFirstRun = async (targetRoot) => {
|
|
|
954
1179
|
);
|
|
955
1180
|
}
|
|
956
1181
|
const sourceLabel = sourceParts.length > 0 ? sourceParts.join(" + ") : "configured HR sources";
|
|
957
|
-
process.stdout.write(
|
|
958
|
-
`
|
|
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
|
+
);
|
|
959
1188
|
try {
|
|
960
1189
|
const pythonCommand = resolvePythonCommand();
|
|
961
1190
|
const scriptPath = path.join(targetRoot, "bin", "sync_sources.py");
|
|
@@ -982,12 +1211,10 @@ const syncHrSourceInventoryOnFirstRun = async (targetRoot) => {
|
|
|
982
1211
|
});
|
|
983
1212
|
const summary = stdout.trim();
|
|
984
1213
|
if (code === 0) {
|
|
985
|
-
|
|
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}).
|
|
986
1217
|
`);
|
|
987
|
-
process.stdout.write(
|
|
988
|
-
`[agenthub] HR source status: ${path.join(targetRoot, "source-status.json")}
|
|
989
|
-
`
|
|
990
|
-
);
|
|
991
1218
|
return;
|
|
992
1219
|
}
|
|
993
1220
|
process.stderr.write(
|
|
@@ -1007,18 +1234,33 @@ const syncHrSourceInventoryOnFirstRun = async (targetRoot) => {
|
|
|
1007
1234
|
};
|
|
1008
1235
|
const ensureHrOfficeReadyOrBootstrap = async (targetRoot = defaultHrHome(), options = {}) => {
|
|
1009
1236
|
if (await hrHomeInitialized(targetRoot)) return;
|
|
1010
|
-
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
|
+
}
|
|
1011
1250
|
const hrModelSelection = shouldPrompt ? await promptHrBootstrapModelSelection(targetRoot) : void 0;
|
|
1012
|
-
await
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
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}
|
|
1017
1255
|
`);
|
|
1018
1256
|
printHrModelOverrideHint(targetRoot);
|
|
1019
1257
|
if (options.syncSourcesOnFirstRun ?? true) {
|
|
1020
1258
|
await syncHrSourceInventoryOnFirstRun(targetRoot);
|
|
1021
1259
|
}
|
|
1260
|
+
process.stdout.write(`
|
|
1261
|
+
\u2713 HR Office is ready.
|
|
1262
|
+
`);
|
|
1263
|
+
suppressNextHrRuntimeBanner = true;
|
|
1022
1264
|
};
|
|
1023
1265
|
const isHrRuntimeSelection = (selection) => selection?.kind === "profile" && selection.profile === "hr";
|
|
1024
1266
|
const normalizeCsv = (value) => value.split(",").map((item) => item.trim()).filter(Boolean);
|
|
@@ -1190,17 +1432,38 @@ const maybeConfigureEnvrc = async (workspace, configRoot) => {
|
|
|
1190
1432
|
};
|
|
1191
1433
|
const createPromptInterface = () => {
|
|
1192
1434
|
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY);
|
|
1193
|
-
|
|
1435
|
+
const terminal = interactive && shouldUseReadlineTerminal();
|
|
1436
|
+
if (terminal) {
|
|
1194
1437
|
const resetSequence = interactivePromptResetSequence();
|
|
1195
1438
|
if (resetSequence) process.stdout.write(resetSequence);
|
|
1196
1439
|
}
|
|
1197
1440
|
return readline.createInterface({
|
|
1198
1441
|
input: process.stdin,
|
|
1199
1442
|
output: process.stdout,
|
|
1200
|
-
terminal
|
|
1443
|
+
terminal
|
|
1201
1444
|
});
|
|
1202
1445
|
};
|
|
1203
|
-
const
|
|
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
|
+
};
|
|
1204
1467
|
const promptRequired = async (rl, question, defaultValue) => {
|
|
1205
1468
|
while (true) {
|
|
1206
1469
|
const answer = await askPrompt(
|
|
@@ -2260,6 +2523,7 @@ if (parsed.command === "hr") {
|
|
|
2260
2523
|
await ensureHrOfficeReadyOrBootstrap(resolveSelectedHomeRoot(parsed), {
|
|
2261
2524
|
syncSourcesOnFirstRun: !parsed.assembleOnly
|
|
2262
2525
|
});
|
|
2526
|
+
await repairHrModelConfigurationIfNeeded(resolveSelectedHomeRoot(parsed) || defaultHrHome());
|
|
2263
2527
|
if (parsed.hrIntent?.kind === "office") {
|
|
2264
2528
|
parsed.workspace = resolveSelectedHomeRoot(parsed) || defaultHrHome();
|
|
2265
2529
|
} else if (parsed.hrIntent?.kind === "compose") {
|
|
@@ -2284,10 +2548,13 @@ if (parsed.command === "run" || parsed.command === "start" || parsed.command ===
|
|
|
2284
2548
|
}
|
|
2285
2549
|
const finalConfigRoot = resolveConfigRoot(parsed);
|
|
2286
2550
|
const result = await composeSelection(parsed, finalConfigRoot);
|
|
2287
|
-
|
|
2288
|
-
|
|
2289
|
-
|
|
2290
|
-
)
|
|
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;
|
|
2291
2558
|
if (shouldChmod()) {
|
|
2292
2559
|
await chmod(path.join(result.configRoot, "run.sh"), 493);
|
|
2293
2560
|
}
|
|
@@ -9,7 +9,8 @@ const csiSequencePattern = /\u001b\[[0-?]*[ -/]*[@-~]/g;
|
|
|
9
9
|
const oscSequencePattern = /\u001b\][^\u0007\u001b]*(?:\u0007|\u001b\\)/g;
|
|
10
10
|
const singleEscapePattern = /\u001b[@-_]/g;
|
|
11
11
|
const controlCharacterPattern = /[\u0000-\u001f\u007f]/g;
|
|
12
|
-
const
|
|
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, "");
|
|
13
14
|
const interactivePromptResetSequence = (win = detectWindows()) => isWindows(win) ? [
|
|
14
15
|
"\x1B[?1000l",
|
|
15
16
|
"\x1B[?1001l",
|
|
@@ -19,6 +20,7 @@ const interactivePromptResetSequence = (win = detectWindows()) => isWindows(win)
|
|
|
19
20
|
"\x1B[?1006l",
|
|
20
21
|
"\x1B[?1015l"
|
|
21
22
|
].join("") : "";
|
|
23
|
+
const shouldUseReadlineTerminal = (win = detectWindows()) => !isWindows(win);
|
|
22
24
|
const generateRunScript = () => `#!/usr/bin/env bash
|
|
23
25
|
set -euo pipefail
|
|
24
26
|
|
|
@@ -57,6 +59,7 @@ export {
|
|
|
57
59
|
resolvePythonCommand,
|
|
58
60
|
shouldChmod,
|
|
59
61
|
shouldOfferEnvrc,
|
|
62
|
+
shouldUseReadlineTerminal,
|
|
60
63
|
spawnOptions,
|
|
61
64
|
stripTerminalControlInput,
|
|
62
65
|
symlinkType,
|