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.
@@ -37,19 +37,28 @@ import {
37
37
  import { readPackageVersion } from "./package-version.js";
38
38
  import {
39
39
  mergeAgentHubSettingsDefaults,
40
- hrPrimaryAgentName,
41
- hrSubagentNames,
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 Interactive terminals can choose recommended / free / custom
150
- defaults to openai/gpt-5.4-mini when left blank
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 promptHrBootstrapModelSelection = async (hrRoot) => {
844
- const rl = createPromptInterface();
845
- try {
846
- process.stdout.write("\nFirst-time HR Office setup\n");
847
- process.stdout.write("Choose an HR model preset:\n");
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
- ` recommended Use ${recommendedHrBootstrapModel} (${recommendedHrBootstrapVariant})
920
+ `[agenthub] Recommended HR preset requires OpenAI model access in your opencode environment.
850
921
  `
851
922
  );
852
- process.stdout.write(
853
- " free Pick from current opencode free models (quality may drop)\n"
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
- process.stdout.write(" custom Enter any model id yourself\n\n");
856
- const subagentStrategy = await promptChoice(
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
- "HR model preset",
859
- ["recommended", "free", "custom"],
860
- "recommended"
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
- let sharedSubagentModel;
863
- let consoleModel;
864
- if (subagentStrategy === "recommended") {
865
- consoleModel = recommendedHrBootstrapModel;
866
- sharedSubagentModel = recommendedHrBootstrapModel;
867
- }
868
- if (subagentStrategy === "free") {
869
- const freeModels = await listOpencodeFreeModels();
870
- const fallbackFreeModel = freeModels.includes("opencode/minimax-m2.5-free") ? "opencode/minimax-m2.5-free" : freeModels[0] || "opencode/minimax-m2.5-free";
871
- process.stdout.write("Current opencode free models:\n");
872
- sharedSubagentModel = await promptIndexedChoice(
873
- rl,
874
- "Choose a free model for HR",
875
- freeModels.length > 0 ? freeModels : [fallbackFreeModel],
876
- fallbackFreeModel
877
- );
878
- consoleModel = sharedSubagentModel;
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
- "Custom HR model",
883
- recommendedHrBootstrapModel
1033
+ "Apply this recommendation now",
1034
+ ["accept", "recommended", "free", "native", "custom"],
1035
+ "accept"
884
1036
  );
885
- consoleModel = sharedSubagentModel;
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
- process.stdout.write(`
888
- [agenthub] HR settings will be written to ${path.join(hrRoot, "settings.json")}
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
- return {
891
- consoleModel,
892
- subagentStrategy,
893
- sharedSubagentModel
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
- process.stdout.write(
901
- [
902
- `[agenthub] HR model settings: ${path.join(targetRoot, "settings.json")}`,
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(`[agenthub] First run \u2014 syncing HR inventory from ${sourceLabel}...
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
- if (summary) process.stdout.write(`${summary}
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 = process.stdin.isTTY && process.stdout.isTTY;
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 installHrOfficeHomeWithOptions({
1013
- hrRoot: targetRoot,
1014
- hrModelSelection
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
- if (interactive) {
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: interactive
1443
+ terminal
1201
1444
  });
1202
1445
  };
1203
- const askPrompt = async (rl, question) => stripTerminalControlInput(await rl.question(question));
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
- printRuntimeBanner(
2288
- parsed.command === "hr" ? "HR Office" : "My Team",
2289
- resolveSelectedHomeRoot(parsed) || defaultAgentHubHome()
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 stripTerminalControlInput = (value) => value.replace(oscSequencePattern, "").replace(csiSequencePattern, "").replace(singleEscapePattern, "").replace(controlCharacterPattern, "");
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,