@indigoai-us/hq-cli 5.345.16 → 5.345.18
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/CHANGELOG.md +8 -1
- package/assets/scaffold/core/scripts/generate-workers-registry.sh +130 -6
- package/assets/scaffold/core/scripts/hq-session.sh +1 -1
- package/assets/scaffold/core/scripts/lib/hq-cli-floor.sh +342 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +122 -5
- package/assets/scaffold/core/scripts/qmd-reindex-bg.sh +21 -1
- package/assets/scaffold/core/scripts/scan-packages.sh +130 -5
- package/dist/commands/core.js +1 -0
- package/dist/commands/lanes-accounts.js +1 -0
- package/dist/commands/lanes.js +20 -0
- package/dist/lib/core-utils/sentry-report.d.ts +2 -2
- package/dist/lib/core-utils/sentry-report.js +47 -8
- package/dist/lib/lanes/claude-balance.d.ts +5 -1
- package/dist/lib/lanes/claude-balance.js +20 -3
- package/dist/lib/lanes/spawn.js +7 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,10 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.18] — 2026-10-06
|
|
6
|
+
- Git Bash forwarders now keep slash commands intact and convert file path arguments that hq needs on Windows.
|
|
7
|
+
- Lane placement and Claude account-balance refusals now show the requested placement, actual host, balance host when different, and the model values and sources used for an unavailable-account decision.
|
|
8
|
+
|
|
5
9
|
## [5.345.16] — 2026-10-06
|
|
10
|
+
- Claude account model settings are defaults, so explicit lane models remain eligible; explicit account selections on create, resume, and message are recorded as CLI choices.
|
|
11
|
+
- When `hq-anywhere-runtime` is enabled, `hq install --global --runtime claude --via plugin` installs the `hq` plugin from the `indigoai-us/hq-packages` Claude marketplace, and `hq install @indigoai-us/hq-anywhere` installs the Codex pack from that repository. The flag-off path refuses the HQ Anywhere alias without fetching a package source.
|
|
12
|
+
|
|
13
|
+
## [5.345.15] — 2026-10-06
|
|
6
14
|
- Route live hook Sentry reports through the fast CLI path.
|
|
7
15
|
|
|
8
|
-
- Claude account model settings are defaults, so explicit lane models remain eligible; explicit account selections on create, resume, and message are recorded as CLI choices.
|
|
9
16
|
- When `hq-anywhere-runtime` is enabled, `hq install --global --runtime claude --via plugin` installs the `hq` plugin from the `indigoai-us/hq-packages` Claude marketplace, and `hq install @indigoai-us/hq-anywhere` installs the Codex pack from that repository. The flag-off path refuses the HQ Anywhere alias without fetching a package source.
|
|
10
17
|
|
|
11
18
|
## [5.345.14] — 2026-10-06
|
|
@@ -1,13 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# FORWARDER —
|
|
2
|
+
# FORWARDER — the implementation of this script now lives in the hq CLI.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
# as the
|
|
6
|
-
#
|
|
4
|
+
# It ships at assets/scaffold/core/scripts/generate-workers-registry.sh inside @indigoai-us/hq-cli and
|
|
5
|
+
# runs as the hidden command `hq core generate-workers-registry`. This file stays behind so every
|
|
6
|
+
# existing caller — skills, other scripts, CI, and muscle memory — keeps working
|
|
7
|
+
# against the path it already knows.
|
|
8
|
+
#
|
|
9
|
+
# Why the implementation moved: it is a derived-index generator invoked explicitly and by reindex, so
|
|
10
|
+
# process startup is immaterial and the CLI already owns the index primitives.
|
|
11
|
+
#
|
|
12
|
+
# Equivalence is not assumed. The CLI's native implementation is pinned against a
|
|
13
|
+
# byte-frozen copy of this script's pre-migration source by a differential suite
|
|
14
|
+
# (hq-cli e2e/registry-scan-differential.test.ts), which compares exit status,
|
|
15
|
+
# stdout, stderr and a sha256 manifest of every file touched, across repeated runs.
|
|
16
|
+
#
|
|
17
|
+
# The ABI is preserved exactly: arguments are forwarded unchanged, stdin is never
|
|
18
|
+
# read by this file, stdout and stderr are inherited untouched, and the child
|
|
19
|
+
# replaces this process so its exit code and signal disposition become the
|
|
20
|
+
# caller's.
|
|
7
21
|
|
|
8
22
|
set -euo pipefail
|
|
9
23
|
|
|
10
|
-
|
|
24
|
+
FORWARDER_PATH="${BASH_SOURCE[0]}"
|
|
25
|
+
FORWARDER_DIR="${FORWARDER_PATH%/*}"
|
|
26
|
+
[ "$FORWARDER_DIR" != "$FORWARDER_PATH" ] || FORWARDER_DIR=.
|
|
27
|
+
SCRIPT_DIR="$(cd "$FORWARDER_DIR" && pwd)"
|
|
28
|
+
|
|
29
|
+
# This forwarder sits in the tree it targets, so its own location IS the root.
|
|
30
|
+
HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
11
31
|
|
|
12
32
|
if ! command -v hq >/dev/null 2>&1; then
|
|
13
33
|
echo "generate-workers-registry.sh: requires the hq CLI — this script's implementation now ships with it." >&2
|
|
@@ -15,4 +35,108 @@ if ! command -v hq >/dev/null 2>&1; then
|
|
|
15
35
|
exit 127
|
|
16
36
|
fi
|
|
17
37
|
|
|
18
|
-
|
|
38
|
+
if [ -f "$SCRIPT_DIR/lib/hq-cli-floor.sh" ]; then
|
|
39
|
+
# shellcheck source=lib/hq-cli-floor.sh
|
|
40
|
+
. "$SCRIPT_DIR/lib/hq-cli-floor.sh"
|
|
41
|
+
hq_cli_floor_check "generate-workers-registry.sh" "5.78.0"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# MSYS path conversion is disabled for the hq process so slash commands arrive unchanged.
|
|
45
|
+
# Convert only this generated root path to Windows form for the native CLI.
|
|
46
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
47
|
+
HQ_ROOT="$(cygpath -m "$HQ_ROOT")"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
forwarded_args=("$@")
|
|
51
|
+
forwarded_arg_count=$#
|
|
52
|
+
PATH_OPERANDS=none
|
|
53
|
+
convert_forwarded_path() {
|
|
54
|
+
local arg_index="$1"
|
|
55
|
+
local value="${forwarded_args[$arg_index]}"
|
|
56
|
+
case "$value" in
|
|
57
|
+
/*) forwarded_args[$arg_index]="$(cygpath -m "$value")" ;;
|
|
58
|
+
esac
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
62
|
+
IFS=';' read -r -a path_rules <<< "$PATH_OPERANDS"
|
|
63
|
+
for path_rule in "${path_rules[@]}"; do
|
|
64
|
+
case "$path_rule" in
|
|
65
|
+
none) ;;
|
|
66
|
+
position:*)
|
|
67
|
+
index="${path_rule#position:}"
|
|
68
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
69
|
+
;;
|
|
70
|
+
first-nonoption)
|
|
71
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
72
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index"; break ;; esac
|
|
73
|
+
done
|
|
74
|
+
;;
|
|
75
|
+
all-nonoptions)
|
|
76
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
77
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index" ;; esac
|
|
78
|
+
done
|
|
79
|
+
;;
|
|
80
|
+
options:*)
|
|
81
|
+
IFS=, read -r -a path_options <<< "${path_rule#options:}"
|
|
82
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
83
|
+
for path_option in "${path_options[@]}"; do
|
|
84
|
+
case "${forwarded_args[index]}" in
|
|
85
|
+
"$path_option")
|
|
86
|
+
index=$((index + 1))
|
|
87
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
88
|
+
break
|
|
89
|
+
;;
|
|
90
|
+
"$path_option"=*)
|
|
91
|
+
value="${forwarded_args[index]#*=}"
|
|
92
|
+
case "$value" in /*) forwarded_args[index]="${path_option}=$(cygpath -m "$value")" ;; esac
|
|
93
|
+
break
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
done
|
|
97
|
+
done
|
|
98
|
+
;;
|
|
99
|
+
comma:*)
|
|
100
|
+
path_option="${path_rule#comma:}"
|
|
101
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
102
|
+
case "${forwarded_args[index]}" in
|
|
103
|
+
"$path_option")
|
|
104
|
+
index=$((index + 1))
|
|
105
|
+
if (( index < forwarded_arg_count )); then
|
|
106
|
+
IFS=, read -r -a path_values <<< "${forwarded_args[index]}"
|
|
107
|
+
converted_values=()
|
|
108
|
+
for path_value in "${path_values[@]}"; do
|
|
109
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
110
|
+
done
|
|
111
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
112
|
+
forwarded_args[index]="$value"
|
|
113
|
+
fi
|
|
114
|
+
break
|
|
115
|
+
;;
|
|
116
|
+
"$path_option"=*)
|
|
117
|
+
value="${forwarded_args[index]#*=}"
|
|
118
|
+
case "$value" in
|
|
119
|
+
/*,*|/*)
|
|
120
|
+
IFS=, read -r -a path_values <<< "$value"
|
|
121
|
+
converted_values=()
|
|
122
|
+
for path_value in "${path_values[@]}"; do
|
|
123
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
124
|
+
done
|
|
125
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
126
|
+
forwarded_args[index]="${path_option}=$value"
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
break
|
|
130
|
+
;;
|
|
131
|
+
esac
|
|
132
|
+
done
|
|
133
|
+
;;
|
|
134
|
+
*) echo "unknown path_operands rule: $path_rule" >&2; exit 70 ;;
|
|
135
|
+
esac
|
|
136
|
+
done
|
|
137
|
+
fi
|
|
138
|
+
if [ "$forwarded_arg_count" -gt 0 ]; then
|
|
139
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" generate-workers-registry "${forwarded_args[@]}"
|
|
140
|
+
else
|
|
141
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" generate-workers-registry
|
|
142
|
+
fi
|
|
@@ -60,7 +60,7 @@ if [ "${HQ_HQ_SESSION_NO_CLI:-}" != "1" ]; then
|
|
|
60
60
|
case "$__hs_caps" in
|
|
61
61
|
*hq-session*)
|
|
62
62
|
case "$__hs_caps" in
|
|
63
|
-
*exact-company-directory-match-v1*) exec hq core hq-session "$@" ;;
|
|
63
|
+
*exact-company-directory-match-v1*) MSYS2_ARG_CONV_EXCL='*' exec hq core hq-session "$@" ;;
|
|
64
64
|
esac
|
|
65
65
|
;;
|
|
66
66
|
esac
|
|
@@ -0,0 +1,342 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Shared hq-cli version-floor helpers for setup and the standalone hook doctor.
|
|
3
|
+
# Keep this compatible with macOS Bash 3.2 and Linux Bash 5.
|
|
4
|
+
|
|
5
|
+
HQ_CLI_FLOOR_UPGRADE_COMMAND='npm install -g @indigoai-us/hq-cli@latest'
|
|
6
|
+
|
|
7
|
+
# Print the top-level requiresHqCli value from core/core.yaml without yq.
|
|
8
|
+
# Return 1 when the key/file is absent and 2 when the key is duplicated.
|
|
9
|
+
hq_cli_floor_required() {
|
|
10
|
+
local hq_root="$1"
|
|
11
|
+
local core_yaml="${hq_root%/}/core/core.yaml"
|
|
12
|
+
[ -r "$core_yaml" ] || return 1
|
|
13
|
+
|
|
14
|
+
awk '
|
|
15
|
+
BEGIN { found = 0; invalid = 0 }
|
|
16
|
+
/^requiresHqCli[[:space:]]*:/ {
|
|
17
|
+
if (found) { invalid = 1; next }
|
|
18
|
+
found = 1
|
|
19
|
+
value = $0
|
|
20
|
+
sub(/^requiresHqCli[[:space:]]*:[[:space:]]*/, "", value)
|
|
21
|
+
sub(/[[:space:]]+#.*$/, "", value)
|
|
22
|
+
sub(/^[[:space:]]*/, "", value)
|
|
23
|
+
sub(/[[:space:]]*$/, "", value)
|
|
24
|
+
quote = sprintf("%c", 39)
|
|
25
|
+
first = substr(value, 1, 1)
|
|
26
|
+
last = substr(value, length(value), 1)
|
|
27
|
+
if ((first == "\"" && last == "\"") || (first == quote && last == quote)) {
|
|
28
|
+
value = substr(value, 2, length(value) - 2)
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
END {
|
|
32
|
+
if (invalid) exit 2
|
|
33
|
+
if (!found) exit 1
|
|
34
|
+
print value
|
|
35
|
+
}
|
|
36
|
+
' "$core_yaml"
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
# Resolve an executable through symlinks using portable readlink calls.
|
|
40
|
+
_hq_cli_floor_resolve_executable() {
|
|
41
|
+
local executable="$1" link_dir link_target hops=0
|
|
42
|
+
case "$executable" in
|
|
43
|
+
/*) ;;
|
|
44
|
+
*) executable="$PWD/$executable" ;;
|
|
45
|
+
esac
|
|
46
|
+
|
|
47
|
+
while [ -L "$executable" ]; do
|
|
48
|
+
hops=$((hops + 1))
|
|
49
|
+
[ "$hops" -le 40 ] || return 1
|
|
50
|
+
link_dir="${executable%/*}"
|
|
51
|
+
[ -n "$link_dir" ] || link_dir="/"
|
|
52
|
+
link_dir="$(cd -P "$link_dir" 2>/dev/null && pwd)" || return 1
|
|
53
|
+
link_target="$(readlink "$executable" 2>/dev/null)" || return 1
|
|
54
|
+
case "$link_target" in
|
|
55
|
+
/*) executable="$link_target" ;;
|
|
56
|
+
*) executable="$link_dir/$link_target" ;;
|
|
57
|
+
esac
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
[ -f "$executable" ] || return 1
|
|
61
|
+
link_dir="${executable%/*}"
|
|
62
|
+
[ -n "$link_dir" ] || link_dir="/"
|
|
63
|
+
link_dir="$(cd -P "$link_dir" 2>/dev/null && pwd)" || return 1
|
|
64
|
+
printf '%s/%s\n' "${link_dir%/}" "${executable##*/}"
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
_hq_cli_floor_package_version() {
|
|
68
|
+
local package_json="$1"
|
|
69
|
+
awk -F '"' '
|
|
70
|
+
{
|
|
71
|
+
for (i = 2; i + 2 <= NF; i += 4) {
|
|
72
|
+
key = $i
|
|
73
|
+
value = $(i + 2)
|
|
74
|
+
if (key == "name" && package_name == "") package_name = value
|
|
75
|
+
else if (key == "version" && package_version == "") package_version = value
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
END {
|
|
79
|
+
if (package_name == "@indigoai-us/hq-cli" && package_version != "") {
|
|
80
|
+
print package_version
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
' "$package_json"
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
# Read the package version using shell builtins on the warm path. The npm
|
|
87
|
+
# package's top-level version is the first version key in its package.json.
|
|
88
|
+
_hq_cli_floor_package_version_builtin() {
|
|
89
|
+
local package_json="$1" line
|
|
90
|
+
while IFS= read -r line || [ -n "$line" ]; do
|
|
91
|
+
if [[ "$line" =~ \"version\"[[:space:]]*:[[:space:]]*\"([^\"]+)\" ]]; then
|
|
92
|
+
printf '%s\n' "${BASH_REMATCH[1]}"
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
done < "$package_json"
|
|
96
|
+
return 1
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
# Return the installed CLI version without starting another Node process on the
|
|
100
|
+
# hook path. A package's version field is the same version reported by its
|
|
101
|
+
# generated hq entry point. Unrecognized or malformed installations retain the
|
|
102
|
+
# historical --version probe as a fallback.
|
|
103
|
+
_hq_cli_floor_installed_version() {
|
|
104
|
+
local hq_command cached_schema cached_command cached_target cached_package_json cached_version current_package_version
|
|
105
|
+
local cache_dir cache_file resolved dir package_json package_version parent raw_version parsed_version
|
|
106
|
+
|
|
107
|
+
hq_command="$(command -v hq 2>/dev/null)" || return 127
|
|
108
|
+
[ -n "$hq_command" ] || return 127
|
|
109
|
+
|
|
110
|
+
# The cache hit path is intentionally shell-only. Compare the selected hq
|
|
111
|
+
# entry point and its current target with the entry recorded on the cold
|
|
112
|
+
# lookup; -nt also catches an in-place replacement of that target.
|
|
113
|
+
cache_dir="${XDG_CACHE_HOME:-${HOME:-}/.cache}/hq-cli-floor"
|
|
114
|
+
cache_file="$cache_dir/version"
|
|
115
|
+
if [ -r "$cache_file" ]; then
|
|
116
|
+
{
|
|
117
|
+
IFS= read -r cached_schema || cached_schema=""
|
|
118
|
+
IFS= read -r cached_command || cached_command=""
|
|
119
|
+
IFS= read -r cached_target || cached_target=""
|
|
120
|
+
IFS= read -r cached_package_json || cached_package_json=""
|
|
121
|
+
IFS= read -r cached_version || cached_version=""
|
|
122
|
+
} < "$cache_file"
|
|
123
|
+
if [ "$cached_schema" = "hq-cli-floor.v2" ] &&
|
|
124
|
+
[ "$cached_command" = "$hq_command" ] && [ -n "$cached_target" ] &&
|
|
125
|
+
[ -n "$cached_package_json" ] && [ -n "$cached_version" ] &&
|
|
126
|
+
_hq_cli_floor_parse_semver "$cached_version" >/dev/null 2>&1 &&
|
|
127
|
+
[ -e "$cached_target" ] && [ -f "$cached_package_json" ] &&
|
|
128
|
+
[ "$hq_command" -ef "$cached_target" ] &&
|
|
129
|
+
! [[ "$cached_target" -nt "$cache_file" ]] &&
|
|
130
|
+
! [[ "$cached_package_json" -nt "$cache_file" ]] &&
|
|
131
|
+
current_package_version="$( _hq_cli_floor_package_version_builtin "$cached_package_json" )" &&
|
|
132
|
+
[ "$current_package_version" = "$cached_version" ]; then
|
|
133
|
+
printf '%s\n' "$cached_version"
|
|
134
|
+
return 0
|
|
135
|
+
fi
|
|
136
|
+
fi
|
|
137
|
+
|
|
138
|
+
resolved="$( _hq_cli_floor_resolve_executable "$hq_command" 2>/dev/null )" || resolved=""
|
|
139
|
+
if [ -n "$resolved" ]; then
|
|
140
|
+
dir="${resolved%/*}"
|
|
141
|
+
while [ -n "$dir" ] && [ "$dir" != / ]; do
|
|
142
|
+
package_json="$dir/package.json"
|
|
143
|
+
if [ -f "$package_json" ]; then
|
|
144
|
+
package_version="$( _hq_cli_floor_package_version "$package_json" 2>/dev/null )" || package_version=""
|
|
145
|
+
if [ -n "$package_version" ]; then
|
|
146
|
+
if mkdir -p "$cache_dir" 2>/dev/null; then
|
|
147
|
+
local cache_tmp="$cache_file.$$"
|
|
148
|
+
if {
|
|
149
|
+
printf 'hq-cli-floor.v2\n'
|
|
150
|
+
printf '%s\n' "$hq_command"
|
|
151
|
+
printf '%s\n' "$resolved"
|
|
152
|
+
printf '%s\n' "$package_json"
|
|
153
|
+
printf '%s\n' "$package_version"
|
|
154
|
+
} > "$cache_tmp" 2>/dev/null; then
|
|
155
|
+
mv -f "$cache_tmp" "$cache_file" 2>/dev/null || rm -f "$cache_tmp" 2>/dev/null || true
|
|
156
|
+
fi
|
|
157
|
+
fi
|
|
158
|
+
printf '%s\n' "$package_version"
|
|
159
|
+
return 0
|
|
160
|
+
fi
|
|
161
|
+
fi
|
|
162
|
+
parent="${dir%/*}"
|
|
163
|
+
[ -n "$parent" ] || parent="/"
|
|
164
|
+
[ "$parent" != "$dir" ] || break
|
|
165
|
+
dir="$parent"
|
|
166
|
+
done
|
|
167
|
+
raw_version="$("$resolved" --version 2>/dev/null)" || return 1
|
|
168
|
+
else
|
|
169
|
+
raw_version="$(hq --version 2>/dev/null)" || return 1
|
|
170
|
+
fi
|
|
171
|
+
parsed_version="$(printf '%s\n' "$raw_version" | awk '
|
|
172
|
+
match($0, /[v]?[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?(\+[0-9A-Za-z.-]+)?/) {
|
|
173
|
+
version = substr($0, RSTART, RLENGTH)
|
|
174
|
+
sub(/^v/, "", version)
|
|
175
|
+
print version
|
|
176
|
+
exit
|
|
177
|
+
}
|
|
178
|
+
')"
|
|
179
|
+
[ -n "$parsed_version" ] || return 1
|
|
180
|
+
printf '%s\n' "$parsed_version"
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
# Parse strict SemVer fields into private globals for the comparator.
|
|
184
|
+
_hq_cli_floor_parse_semver() {
|
|
185
|
+
local version="$1" core prerelease="" build="" identifier
|
|
186
|
+
local -a identifiers=()
|
|
187
|
+
|
|
188
|
+
case "$version" in
|
|
189
|
+
*+*)
|
|
190
|
+
build="${version#*+}"
|
|
191
|
+
[[ "$build" != *+* ]] || return 1
|
|
192
|
+
version="${version%%+*}"
|
|
193
|
+
[[ "$build" =~ ^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$ ]] || return 1
|
|
194
|
+
;;
|
|
195
|
+
esac
|
|
196
|
+
case "$version" in
|
|
197
|
+
*-*)
|
|
198
|
+
prerelease="${version#*-}"
|
|
199
|
+
version="${version%%-*}"
|
|
200
|
+
[[ "$prerelease" =~ ^[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*$ ]] || return 1
|
|
201
|
+
IFS=. read -r -a identifiers <<< "$prerelease"
|
|
202
|
+
for identifier in "${identifiers[@]}"; do
|
|
203
|
+
if [[ "$identifier" =~ ^[0-9]+$ ]] && [ "${#identifier}" -gt 1 ] && [[ "$identifier" == 0* ]]; then
|
|
204
|
+
return 1
|
|
205
|
+
fi
|
|
206
|
+
done
|
|
207
|
+
;;
|
|
208
|
+
esac
|
|
209
|
+
|
|
210
|
+
core="$version"
|
|
211
|
+
[[ "$core" =~ ^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$ ]] || return 1
|
|
212
|
+
HQ_CLI_FLOOR_MAJOR="${BASH_REMATCH[1]}"
|
|
213
|
+
HQ_CLI_FLOOR_MINOR="${BASH_REMATCH[2]}"
|
|
214
|
+
HQ_CLI_FLOOR_PATCH="${BASH_REMATCH[3]}"
|
|
215
|
+
HQ_CLI_FLOOR_PRERELEASE="$prerelease"
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
_hq_cli_floor_numeric_compare() {
|
|
219
|
+
local left="$1" right="$2"
|
|
220
|
+
if [ "${#left}" -lt "${#right}" ]; then
|
|
221
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
222
|
+
elif [ "${#left}" -gt "${#right}" ]; then
|
|
223
|
+
HQ_CLI_FLOOR_CMP=1
|
|
224
|
+
elif [ "$left" = "$right" ]; then
|
|
225
|
+
HQ_CLI_FLOOR_CMP=0
|
|
226
|
+
elif [[ "$left" > "$right" ]]; then
|
|
227
|
+
HQ_CLI_FLOOR_CMP=1
|
|
228
|
+
else
|
|
229
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
230
|
+
fi
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
_hq_cli_floor_prerelease_compare() {
|
|
234
|
+
local left="$1" right="$2" i left_id right_id left_num right_num
|
|
235
|
+
local -a left_parts=() right_parts=()
|
|
236
|
+
IFS=. read -r -a left_parts <<< "$left"
|
|
237
|
+
IFS=. read -r -a right_parts <<< "$right"
|
|
238
|
+
|
|
239
|
+
for ((i = 0; i < ${#left_parts[@]} && i < ${#right_parts[@]}; i++)); do
|
|
240
|
+
left_id="${left_parts[$i]}"
|
|
241
|
+
right_id="${right_parts[$i]}"
|
|
242
|
+
left_num=0
|
|
243
|
+
right_num=0
|
|
244
|
+
[[ "$left_id" =~ ^[0-9]+$ ]] && left_num=1
|
|
245
|
+
[[ "$right_id" =~ ^[0-9]+$ ]] && right_num=1
|
|
246
|
+
if [ "$left_num" -eq 1 ] && [ "$right_num" -eq 1 ]; then
|
|
247
|
+
_hq_cli_floor_numeric_compare "$left_id" "$right_id"
|
|
248
|
+
elif [ "$left_num" -eq 1 ]; then
|
|
249
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
250
|
+
elif [ "$right_num" -eq 1 ]; then
|
|
251
|
+
HQ_CLI_FLOOR_CMP=1
|
|
252
|
+
elif [ "$left_id" = "$right_id" ]; then
|
|
253
|
+
HQ_CLI_FLOOR_CMP=0
|
|
254
|
+
elif [[ "$left_id" > "$right_id" ]]; then
|
|
255
|
+
HQ_CLI_FLOOR_CMP=1
|
|
256
|
+
else
|
|
257
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
258
|
+
fi
|
|
259
|
+
[ "$HQ_CLI_FLOOR_CMP" -eq 0 ] || return 0
|
|
260
|
+
done
|
|
261
|
+
|
|
262
|
+
if [ "${#left_parts[@]}" -lt "${#right_parts[@]}" ]; then
|
|
263
|
+
HQ_CLI_FLOOR_CMP=-1
|
|
264
|
+
elif [ "${#left_parts[@]}" -gt "${#right_parts[@]}" ]; then
|
|
265
|
+
HQ_CLI_FLOOR_CMP=1
|
|
266
|
+
else
|
|
267
|
+
HQ_CLI_FLOOR_CMP=0
|
|
268
|
+
fi
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
# Pure-Bash SemVer comparison. Returns 0 when have >= want, 1 when older, 2 if invalid.
|
|
272
|
+
hq_cli_version_ge() {
|
|
273
|
+
local LC_ALL=C field left right have_prerelease want_prerelease
|
|
274
|
+
_hq_cli_floor_parse_semver "$1" || return 2
|
|
275
|
+
local have_major="$HQ_CLI_FLOOR_MAJOR" have_minor="$HQ_CLI_FLOOR_MINOR"
|
|
276
|
+
local have_patch="$HQ_CLI_FLOOR_PATCH"
|
|
277
|
+
have_prerelease="$HQ_CLI_FLOOR_PRERELEASE"
|
|
278
|
+
|
|
279
|
+
_hq_cli_floor_parse_semver "$2" || return 2
|
|
280
|
+
want_prerelease="$HQ_CLI_FLOOR_PRERELEASE"
|
|
281
|
+
|
|
282
|
+
for field in major minor patch; do
|
|
283
|
+
case "$field" in
|
|
284
|
+
major) left="$have_major"; right="$HQ_CLI_FLOOR_MAJOR" ;;
|
|
285
|
+
minor) left="$have_minor"; right="$HQ_CLI_FLOOR_MINOR" ;;
|
|
286
|
+
patch) left="$have_patch"; right="$HQ_CLI_FLOOR_PATCH" ;;
|
|
287
|
+
esac
|
|
288
|
+
_hq_cli_floor_numeric_compare "$left" "$right"
|
|
289
|
+
if [ "$HQ_CLI_FLOOR_CMP" -gt 0 ]; then
|
|
290
|
+
return 0
|
|
291
|
+
elif [ "$HQ_CLI_FLOOR_CMP" -lt 0 ]; then
|
|
292
|
+
return 1
|
|
293
|
+
fi
|
|
294
|
+
done
|
|
295
|
+
|
|
296
|
+
if [ -z "$have_prerelease" ]; then
|
|
297
|
+
return 0
|
|
298
|
+
elif [ -z "$want_prerelease" ]; then
|
|
299
|
+
return 1
|
|
300
|
+
fi
|
|
301
|
+
|
|
302
|
+
_hq_cli_floor_prerelease_compare "$have_prerelease" "$want_prerelease"
|
|
303
|
+
[ "$HQ_CLI_FLOOR_CMP" -ge 0 ]
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
# Return 0 when hq satisfies the floor; otherwise print the required message and return 127.
|
|
307
|
+
hq_cli_floor_check() {
|
|
308
|
+
local script_name="$1" required="$2" minimum have compare_status
|
|
309
|
+
if ! command -v hq >/dev/null 2>&1; then
|
|
310
|
+
printf '%s: requires the hq CLI — this script\047s implementation now ships with it.\n' "$script_name" >&2
|
|
311
|
+
printf 'Install it with: npm install -g @indigoai-us/hq-cli\n' >&2
|
|
312
|
+
return 127
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
case "$required" in
|
|
316
|
+
'>='*) minimum="${required#>=}" ;;
|
|
317
|
+
*) minimum="$required" ;;
|
|
318
|
+
esac
|
|
319
|
+
if ! _hq_cli_floor_parse_semver "$minimum"; then
|
|
320
|
+
printf '%s: requiresHqCli has unsupported value %s; expected >=X.Y.Z.\n' "$script_name" "$required" >&2
|
|
321
|
+
return 64
|
|
322
|
+
fi
|
|
323
|
+
|
|
324
|
+
have="$( _hq_cli_floor_installed_version )" || have=""
|
|
325
|
+
if [ -z "$have" ]; then
|
|
326
|
+
printf '%s: could not read hq --version; required hq-cli >= %s.\n' "$script_name" "$minimum" >&2
|
|
327
|
+
return 126
|
|
328
|
+
fi
|
|
329
|
+
if hq_cli_version_ge "$have" "$minimum"; then
|
|
330
|
+
return 0
|
|
331
|
+
else
|
|
332
|
+
compare_status=$?
|
|
333
|
+
fi
|
|
334
|
+
if [ "$compare_status" -gt 1 ]; then
|
|
335
|
+
printf '%s: hq --version returned invalid SemVer %s; required hq-cli >= %s.\n' "$script_name" "$have" "$minimum" >&2
|
|
336
|
+
return 126
|
|
337
|
+
fi
|
|
338
|
+
|
|
339
|
+
printf '%s: this script needs hq-cli >= %s (found %s); upgrade with: %s\n' \
|
|
340
|
+
"$script_name" "$minimum" "$have" "$HQ_CLI_FLOOR_UPGRADE_COMMAND" >&2
|
|
341
|
+
return 127
|
|
342
|
+
}
|
|
@@ -1,16 +1,133 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# FORWARDER — the implementation of this script lives in the hq CLI.
|
|
2
|
+
# FORWARDER — the implementation of this script now lives in the hq CLI.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
# `hq core qmd-reindex-after-sync
|
|
6
|
-
#
|
|
4
|
+
# It ships at assets/scaffold/core/scripts/qmd-reindex-after-sync.sh inside @indigoai-us/hq-cli and
|
|
5
|
+
# runs as the hidden command `hq core qmd-reindex-after-sync`. This file stays behind so every
|
|
6
|
+
# existing caller — skills, other scripts, CI, and muscle memory — keeps working
|
|
7
|
+
# against the path it already knows.
|
|
8
|
+
#
|
|
9
|
+
# Why the implementation moved: it is a cold, explicitly-invoked operation where
|
|
10
|
+
# process startup is immaterial, so hosting it in the CLI costs nothing and
|
|
11
|
+
# keeps the scaffold to configuration rather than code.
|
|
12
|
+
#
|
|
13
|
+
# The ABI is preserved exactly: arguments are forwarded unchanged, stdin is never
|
|
14
|
+
# read by this file, stdout and stderr are inherited untouched, and the child
|
|
15
|
+
# replaces this process so its exit code and signal disposition become the
|
|
16
|
+
# caller's.
|
|
7
17
|
|
|
8
18
|
set -euo pipefail
|
|
9
19
|
|
|
20
|
+
FORWARDER_PATH="${BASH_SOURCE[0]}"
|
|
21
|
+
FORWARDER_DIR="${FORWARDER_PATH%/*}"
|
|
22
|
+
[ "$FORWARDER_DIR" != "$FORWARDER_PATH" ] || FORWARDER_DIR=.
|
|
23
|
+
SCRIPT_DIR="$(cd "$FORWARDER_DIR" && pwd)"
|
|
24
|
+
|
|
25
|
+
# No root is injected: this script derived its root from the CALLER's cwd
|
|
26
|
+
# before it moved (git top level, a cwd walk, or a positional argument), and the
|
|
27
|
+
# CLI preserves that. Anything the caller already exported still applies.
|
|
28
|
+
|
|
10
29
|
if ! command -v hq >/dev/null 2>&1; then
|
|
11
30
|
echo "qmd-reindex-after-sync.sh: requires the hq CLI — this script's implementation now ships with it." >&2
|
|
12
31
|
echo "Install it with: npm install -g @indigoai-us/hq-cli" >&2
|
|
13
32
|
exit 127
|
|
14
33
|
fi
|
|
15
34
|
|
|
16
|
-
|
|
35
|
+
if [ -f "$SCRIPT_DIR/lib/hq-cli-floor.sh" ]; then
|
|
36
|
+
# shellcheck source=lib/hq-cli-floor.sh
|
|
37
|
+
. "$SCRIPT_DIR/lib/hq-cli-floor.sh"
|
|
38
|
+
hq_cli_floor_check "qmd-reindex-after-sync.sh" "5.78.0"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
forwarded_args=("$@")
|
|
42
|
+
forwarded_arg_count=$#
|
|
43
|
+
PATH_OPERANDS=first-nonoption
|
|
44
|
+
convert_forwarded_path() {
|
|
45
|
+
local arg_index="$1"
|
|
46
|
+
local value="${forwarded_args[$arg_index]}"
|
|
47
|
+
case "$value" in
|
|
48
|
+
/*) forwarded_args[$arg_index]="$(cygpath -m "$value")" ;;
|
|
49
|
+
esac
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
53
|
+
IFS=';' read -r -a path_rules <<< "$PATH_OPERANDS"
|
|
54
|
+
for path_rule in "${path_rules[@]}"; do
|
|
55
|
+
case "$path_rule" in
|
|
56
|
+
none) ;;
|
|
57
|
+
position:*)
|
|
58
|
+
index="${path_rule#position:}"
|
|
59
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
60
|
+
;;
|
|
61
|
+
first-nonoption)
|
|
62
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
63
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index"; break ;; esac
|
|
64
|
+
done
|
|
65
|
+
;;
|
|
66
|
+
all-nonoptions)
|
|
67
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
68
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index" ;; esac
|
|
69
|
+
done
|
|
70
|
+
;;
|
|
71
|
+
options:*)
|
|
72
|
+
IFS=, read -r -a path_options <<< "${path_rule#options:}"
|
|
73
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
74
|
+
for path_option in "${path_options[@]}"; do
|
|
75
|
+
case "${forwarded_args[index]}" in
|
|
76
|
+
"$path_option")
|
|
77
|
+
index=$((index + 1))
|
|
78
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
79
|
+
break
|
|
80
|
+
;;
|
|
81
|
+
"$path_option"=*)
|
|
82
|
+
value="${forwarded_args[index]#*=}"
|
|
83
|
+
case "$value" in /*) forwarded_args[index]="${path_option}=$(cygpath -m "$value")" ;; esac
|
|
84
|
+
break
|
|
85
|
+
;;
|
|
86
|
+
esac
|
|
87
|
+
done
|
|
88
|
+
done
|
|
89
|
+
;;
|
|
90
|
+
comma:*)
|
|
91
|
+
path_option="${path_rule#comma:}"
|
|
92
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
93
|
+
case "${forwarded_args[index]}" in
|
|
94
|
+
"$path_option")
|
|
95
|
+
index=$((index + 1))
|
|
96
|
+
if (( index < forwarded_arg_count )); then
|
|
97
|
+
IFS=, read -r -a path_values <<< "${forwarded_args[index]}"
|
|
98
|
+
converted_values=()
|
|
99
|
+
for path_value in "${path_values[@]}"; do
|
|
100
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
101
|
+
done
|
|
102
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
103
|
+
forwarded_args[index]="$value"
|
|
104
|
+
fi
|
|
105
|
+
break
|
|
106
|
+
;;
|
|
107
|
+
"$path_option"=*)
|
|
108
|
+
value="${forwarded_args[index]#*=}"
|
|
109
|
+
case "$value" in
|
|
110
|
+
/*,*|/*)
|
|
111
|
+
IFS=, read -r -a path_values <<< "$value"
|
|
112
|
+
converted_values=()
|
|
113
|
+
for path_value in "${path_values[@]}"; do
|
|
114
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
115
|
+
done
|
|
116
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
117
|
+
forwarded_args[index]="${path_option}=$value"
|
|
118
|
+
;;
|
|
119
|
+
esac
|
|
120
|
+
break
|
|
121
|
+
;;
|
|
122
|
+
esac
|
|
123
|
+
done
|
|
124
|
+
;;
|
|
125
|
+
*) echo "unknown path_operands rule: $path_rule" >&2; exit 70 ;;
|
|
126
|
+
esac
|
|
127
|
+
done
|
|
128
|
+
fi
|
|
129
|
+
if [ "$forwarded_arg_count" -gt 0 ]; then
|
|
130
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core qmd-reindex-after-sync "${forwarded_args[@]}"
|
|
131
|
+
else
|
|
132
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core qmd-reindex-after-sync
|
|
133
|
+
fi
|
|
@@ -37,4 +37,24 @@ if ! command -v hq >/dev/null 2>&1; then
|
|
|
37
37
|
exit 127
|
|
38
38
|
fi
|
|
39
39
|
|
|
40
|
-
|
|
40
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
41
|
+
HQ_ROOT="$(cygpath -m "$HQ_ROOT")"
|
|
42
|
+
fi
|
|
43
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
44
|
+
HQ_ROOT="$(cygpath -m "$HQ_ROOT")"
|
|
45
|
+
fi
|
|
46
|
+
forwarded_args=("$@")
|
|
47
|
+
forwarded_arg_count=$#
|
|
48
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
49
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
50
|
+
case "${forwarded_args[index]}" in
|
|
51
|
+
--log) index=$((index + 1)); if ((index < forwarded_arg_count)); then forwarded_args[index]="$(cygpath -m "${forwarded_args[index]}")"; fi ;;
|
|
52
|
+
--log=*) forwarded_args[index]="--log=$(cygpath -m "${forwarded_args[index]#--log=}")" ;;
|
|
53
|
+
esac
|
|
54
|
+
done
|
|
55
|
+
fi
|
|
56
|
+
if [ "$forwarded_arg_count" -gt 0 ]; then
|
|
57
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq index background --hq-root "$HQ_ROOT" "${forwarded_args[@]}"
|
|
58
|
+
else
|
|
59
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq index background --hq-root "$HQ_ROOT"
|
|
60
|
+
fi
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# FORWARDER —
|
|
2
|
+
# FORWARDER — the implementation of this script now lives in the hq CLI.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# It ships at assets/scaffold/core/scripts/scan-packages.sh inside @indigoai-us/hq-cli and
|
|
5
|
+
# runs as the hidden command `hq core scan-packages`. This file stays behind so every
|
|
6
|
+
# existing caller — skills, other scripts, CI, and muscle memory — keeps working
|
|
7
|
+
# against the path it already knows.
|
|
8
|
+
#
|
|
9
|
+
# Why the implementation moved: the pack contribution mapping it encoded is single-sourced in the CLI
|
|
10
|
+
# (src/utils/pack-contributions.ts); keeping a second copy here invited drift.
|
|
11
|
+
#
|
|
12
|
+
# Equivalence is not assumed. The CLI's native implementation is pinned against a
|
|
13
|
+
# byte-frozen copy of this script's pre-migration source by a differential suite
|
|
14
|
+
# (hq-cli e2e/registry-scan-differential.test.ts), which compares exit status,
|
|
15
|
+
# stdout, stderr and a sha256 manifest of every file touched, across repeated runs.
|
|
16
|
+
#
|
|
17
|
+
# The ABI is preserved exactly: arguments are forwarded unchanged, stdin is never
|
|
18
|
+
# read by this file, stdout and stderr are inherited untouched, and the child
|
|
19
|
+
# replaces this process so its exit code and signal disposition become the
|
|
20
|
+
# caller's.
|
|
6
21
|
|
|
7
22
|
set -euo pipefail
|
|
8
23
|
|
|
9
|
-
|
|
24
|
+
FORWARDER_PATH="${BASH_SOURCE[0]}"
|
|
25
|
+
FORWARDER_DIR="${FORWARDER_PATH%/*}"
|
|
26
|
+
[ "$FORWARDER_DIR" != "$FORWARDER_PATH" ] || FORWARDER_DIR=.
|
|
27
|
+
SCRIPT_DIR="$(cd "$FORWARDER_DIR" && pwd)"
|
|
28
|
+
|
|
29
|
+
# This forwarder sits in the tree it targets, so its own location IS the root.
|
|
30
|
+
HQ_ROOT="${HQ_ROOT:-$(cd "$SCRIPT_DIR/../.." && pwd)}"
|
|
10
31
|
|
|
11
32
|
if ! command -v hq >/dev/null 2>&1; then
|
|
12
33
|
echo "scan-packages.sh: requires the hq CLI — this script's implementation now ships with it." >&2
|
|
@@ -14,4 +35,108 @@ if ! command -v hq >/dev/null 2>&1; then
|
|
|
14
35
|
exit 127
|
|
15
36
|
fi
|
|
16
37
|
|
|
17
|
-
|
|
38
|
+
if [ -f "$SCRIPT_DIR/lib/hq-cli-floor.sh" ]; then
|
|
39
|
+
# shellcheck source=lib/hq-cli-floor.sh
|
|
40
|
+
. "$SCRIPT_DIR/lib/hq-cli-floor.sh"
|
|
41
|
+
hq_cli_floor_check "scan-packages.sh" "5.78.0"
|
|
42
|
+
fi
|
|
43
|
+
|
|
44
|
+
# MSYS path conversion is disabled for the hq process so slash commands arrive unchanged.
|
|
45
|
+
# Convert only this generated root path to Windows form for the native CLI.
|
|
46
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
47
|
+
HQ_ROOT="$(cygpath -m "$HQ_ROOT")"
|
|
48
|
+
fi
|
|
49
|
+
|
|
50
|
+
forwarded_args=("$@")
|
|
51
|
+
forwarded_arg_count=$#
|
|
52
|
+
PATH_OPERANDS=none
|
|
53
|
+
convert_forwarded_path() {
|
|
54
|
+
local arg_index="$1"
|
|
55
|
+
local value="${forwarded_args[$arg_index]}"
|
|
56
|
+
case "$value" in
|
|
57
|
+
/*) forwarded_args[$arg_index]="$(cygpath -m "$value")" ;;
|
|
58
|
+
esac
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
if command -v cygpath >/dev/null 2>&1; then
|
|
62
|
+
IFS=';' read -r -a path_rules <<< "$PATH_OPERANDS"
|
|
63
|
+
for path_rule in "${path_rules[@]}"; do
|
|
64
|
+
case "$path_rule" in
|
|
65
|
+
none) ;;
|
|
66
|
+
position:*)
|
|
67
|
+
index="${path_rule#position:}"
|
|
68
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
69
|
+
;;
|
|
70
|
+
first-nonoption)
|
|
71
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
72
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index"; break ;; esac
|
|
73
|
+
done
|
|
74
|
+
;;
|
|
75
|
+
all-nonoptions)
|
|
76
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
77
|
+
case "${forwarded_args[index]}" in --*) ;; *) convert_forwarded_path "$index" ;; esac
|
|
78
|
+
done
|
|
79
|
+
;;
|
|
80
|
+
options:*)
|
|
81
|
+
IFS=, read -r -a path_options <<< "${path_rule#options:}"
|
|
82
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
83
|
+
for path_option in "${path_options[@]}"; do
|
|
84
|
+
case "${forwarded_args[index]}" in
|
|
85
|
+
"$path_option")
|
|
86
|
+
index=$((index + 1))
|
|
87
|
+
if (( index < forwarded_arg_count )); then convert_forwarded_path "$index"; fi
|
|
88
|
+
break
|
|
89
|
+
;;
|
|
90
|
+
"$path_option"=*)
|
|
91
|
+
value="${forwarded_args[index]#*=}"
|
|
92
|
+
case "$value" in /*) forwarded_args[index]="${path_option}=$(cygpath -m "$value")" ;; esac
|
|
93
|
+
break
|
|
94
|
+
;;
|
|
95
|
+
esac
|
|
96
|
+
done
|
|
97
|
+
done
|
|
98
|
+
;;
|
|
99
|
+
comma:*)
|
|
100
|
+
path_option="${path_rule#comma:}"
|
|
101
|
+
for ((index = 0; index < forwarded_arg_count; index++)); do
|
|
102
|
+
case "${forwarded_args[index]}" in
|
|
103
|
+
"$path_option")
|
|
104
|
+
index=$((index + 1))
|
|
105
|
+
if (( index < forwarded_arg_count )); then
|
|
106
|
+
IFS=, read -r -a path_values <<< "${forwarded_args[index]}"
|
|
107
|
+
converted_values=()
|
|
108
|
+
for path_value in "${path_values[@]}"; do
|
|
109
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
110
|
+
done
|
|
111
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
112
|
+
forwarded_args[index]="$value"
|
|
113
|
+
fi
|
|
114
|
+
break
|
|
115
|
+
;;
|
|
116
|
+
"$path_option"=*)
|
|
117
|
+
value="${forwarded_args[index]#*=}"
|
|
118
|
+
case "$value" in
|
|
119
|
+
/*,*|/*)
|
|
120
|
+
IFS=, read -r -a path_values <<< "$value"
|
|
121
|
+
converted_values=()
|
|
122
|
+
for path_value in "${path_values[@]}"; do
|
|
123
|
+
case "$path_value" in /*) converted_values+=("$(cygpath -m "$path_value")") ;; *) converted_values+=("$path_value") ;; esac
|
|
124
|
+
done
|
|
125
|
+
value="$(IFS=,; printf '%s' "${converted_values[*]}")"
|
|
126
|
+
forwarded_args[index]="${path_option}=$value"
|
|
127
|
+
;;
|
|
128
|
+
esac
|
|
129
|
+
break
|
|
130
|
+
;;
|
|
131
|
+
esac
|
|
132
|
+
done
|
|
133
|
+
;;
|
|
134
|
+
*) echo "unknown path_operands rule: $path_rule" >&2; exit 70 ;;
|
|
135
|
+
esac
|
|
136
|
+
done
|
|
137
|
+
fi
|
|
138
|
+
if [ "$forwarded_arg_count" -gt 0 ]; then
|
|
139
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" scan-packages "${forwarded_args[@]}"
|
|
140
|
+
else
|
|
141
|
+
MSYS2_ARG_CONV_EXCL='*' exec hq core --hq-root "$HQ_ROOT" scan-packages
|
|
142
|
+
fi
|
package/dist/commands/core.js
CHANGED
|
@@ -252,6 +252,7 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
252
252
|
{ asset: "core/scripts/hook-lib.sh", root: "live", summary: "Shared shell primitives sourced by the checkpoint gate" },
|
|
253
253
|
{ asset: "core/scripts/lib/session-scope-capability.sh", root: "live", summary: "Session scope-capability lib sourced by hq-session" },
|
|
254
254
|
{ asset: "core/scripts/lib/session-id.sh", root: "live", summary: "Session-id resolver lib sourced by hq-session" },
|
|
255
|
+
{ asset: "core/scripts/lib/hq-cli-floor.sh", root: "live", summary: "Shared CLI version-floor helper sourced by generated forwarders" },
|
|
255
256
|
{ asset: "core/scripts/lib/work-mesh-enqueue.sh", root: "live", summary: "Work Mesh Live spool enqueue lib sourced by hooks" },
|
|
256
257
|
{ asset: "core/scripts/lib/work-mesh-live-hook.sh", root: "live", summary: "Work Mesh Live hook helpers sourced by rebind" },
|
|
257
258
|
{ asset: "core/scripts/lib/work-mesh-live-rebind.sh", root: "live", summary: "Work Mesh Live session rebind library sourced by hooks and the native rollback command" },
|
|
@@ -197,6 +197,7 @@ export function createLaneAccountsHandlers(deps) {
|
|
|
197
197
|
target_host: host.capacity.host,
|
|
198
198
|
error: isLanesError(err) ? err.error : "claude_account_balance_selection_failed",
|
|
199
199
|
message: dispatchErrorMessage(err),
|
|
200
|
+
...(isLanesError(err) ? { details: err.details } : {}),
|
|
200
201
|
};
|
|
201
202
|
}
|
|
202
203
|
});
|
package/dist/commands/lanes.js
CHANGED
|
@@ -3472,14 +3472,20 @@ async function runCreate(ctx, opts) {
|
|
|
3472
3472
|
const targetHost = placementBalanceHostOverride ?? placementHostOverride ?? (outpostId !== undefined
|
|
3473
3473
|
? { kind: "outpost", id: outpostId }
|
|
3474
3474
|
: laneHostFromCtx(ctx));
|
|
3475
|
+
const actualTargetHost = placementHostOverride ?? (outpostId !== undefined
|
|
3476
|
+
? { kind: "outpost", id: outpostId }
|
|
3477
|
+
: { kind: "local", id: ctx.hostname() });
|
|
3475
3478
|
balanceTargetHost = targetHost;
|
|
3476
3479
|
balanceHostLoad = await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config);
|
|
3477
3480
|
const selected = selectClaudeAccount({
|
|
3478
3481
|
hosts: balanceHostLoad.snapshots,
|
|
3479
3482
|
targetHost,
|
|
3483
|
+
actualTargetHost,
|
|
3484
|
+
requestedPlacement: placementToApply ?? null,
|
|
3480
3485
|
config,
|
|
3481
3486
|
quotaState: loadQuotaState(hqRoot),
|
|
3482
3487
|
...(modelToValidate !== undefined ? { model: modelToValidate } : {}),
|
|
3488
|
+
requestedModelSource: cliModel !== undefined ? "flag" : profileModel !== undefined ? "profile" : "default",
|
|
3483
3489
|
recentUsageEnabled: await readClaudeAccountBalanceSignalsEnabled(ctx, company ?? undefined),
|
|
3484
3490
|
now: ctx.now(),
|
|
3485
3491
|
...(allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
@@ -3523,12 +3529,18 @@ async function runCreate(ctx, opts) {
|
|
|
3523
3529
|
const hosts = balanceHostLoad.snapshots.map((host) => host.capacity.host.kind === localHost.kind && host.capacity.host.id === localHost.id
|
|
3524
3530
|
? refreshedLocal
|
|
3525
3531
|
: host);
|
|
3532
|
+
const actualTargetHostForPersist = placementHostOverride ?? (outpostId !== undefined
|
|
3533
|
+
? { kind: "outpost", id: outpostId }
|
|
3534
|
+
: { kind: "local", id: ctx.hostname() });
|
|
3526
3535
|
const selected = selectClaudeAccount({
|
|
3527
3536
|
hosts,
|
|
3528
3537
|
targetHost: balanceTargetHost,
|
|
3538
|
+
actualTargetHost: actualTargetHostForPersist,
|
|
3539
|
+
requestedPlacement: placementToApply ?? null,
|
|
3529
3540
|
config,
|
|
3530
3541
|
quotaState: loadQuotaState(hqRoot),
|
|
3531
3542
|
...(modelToValidate !== undefined ? { model: modelToValidate } : {}),
|
|
3543
|
+
requestedModelSource: cliModel !== undefined ? "flag" : profileModel !== undefined ? "profile" : "default",
|
|
3532
3544
|
recentUsageEnabled: await readClaudeAccountBalanceSignalsEnabled(ctx, company ?? undefined),
|
|
3533
3545
|
now: ctx.now(),
|
|
3534
3546
|
...(allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
@@ -6802,6 +6814,11 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
6802
6814
|
// Repair keeps an account-derived model pinned through the move; plain
|
|
6803
6815
|
// automatic rebalancing resolves each candidate's account default itself.
|
|
6804
6816
|
const selectionModel = preservedModel ?? pinnedModel ?? profileModel;
|
|
6817
|
+
const selectionModelSource = preservedModel !== undefined || pinnedModel !== undefined
|
|
6818
|
+
? "lane"
|
|
6819
|
+
: profileModel !== undefined
|
|
6820
|
+
? "profile"
|
|
6821
|
+
: "default";
|
|
6805
6822
|
const model = resolveLaneModel({
|
|
6806
6823
|
cliModel: pinnedModel,
|
|
6807
6824
|
profileModel,
|
|
@@ -6829,11 +6846,14 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
|
|
|
6829
6846
|
const selectionInput = {
|
|
6830
6847
|
hosts: balanceHosts.snapshots,
|
|
6831
6848
|
targetHost: lane.host,
|
|
6849
|
+
actualTargetHost: lane.host,
|
|
6850
|
+
requestedPlacement: null,
|
|
6832
6851
|
config,
|
|
6833
6852
|
quotaState: loadQuotaState(hqRoot),
|
|
6834
6853
|
recentUsageEnabled: await readClaudeAccountBalanceSignalsEnabled(ctx, lane.company ?? undefined),
|
|
6835
6854
|
now: ctx.now(),
|
|
6836
6855
|
...(selectionModel !== undefined ? { model: selectionModel } : {}),
|
|
6856
|
+
requestedModelSource: selectionModelSource,
|
|
6837
6857
|
unavailableHosts: balanceHosts.unavailableHosts,
|
|
6838
6858
|
};
|
|
6839
6859
|
let selected;
|
|
@@ -26,7 +26,7 @@ export declare const MAX_SENTRY_REPORT_METADATA_VALUE_BYTES = 256;
|
|
|
26
26
|
* is owner-approved telemetry for the exact hook script and may include a
|
|
27
27
|
* company directory; it is never a substitute for hook input or a transcript.
|
|
28
28
|
*/
|
|
29
|
-
export declare const ALLOWED_SENTRY_REPORT_METADATA_KEYS: readonly ["hook_name", "hook_path", "hook_event", "hook_script", "tool_name", "session_id", "declared_timeout_ms", "elapsed_ms", "remaining_ms", "watchdog_timeout_ms", "hook_timeout_ms", "hq_version", "platform", "load_average", "spawn_ms", "slow_child", "slow_child_ms", "slow_phase", "exit_code", "final_elapsed_ms", "bash_env_set", "cwd_kind", "shell", "nproc", "hook_sequence", "hook_timeout_debug_context"];
|
|
29
|
+
export declare const ALLOWED_SENTRY_REPORT_METADATA_KEYS: readonly ["hook_name", "hook_path", "hook_event", "hook_script", "tool_name", "session_id", "declared_timeout_ms", "elapsed_ms", "remaining_ms", "watchdog_timeout_ms", "hook_timeout_ms", "hq_version", "platform", "load_average", "spawn_ms", "slow_child", "slow_child_ms", "slow_phase", "exit_code", "final_elapsed_ms", "bash_env_set", "cwd_kind", "root_storage_class", "shell", "nproc", "hook_sequence", "hook_timeout_debug_context"];
|
|
30
30
|
export declare const MAX_SENTRY_REPORT_METADATA_KEYS: number;
|
|
31
31
|
export type HookSequenceEntry = {
|
|
32
32
|
script: string;
|
|
@@ -47,7 +47,7 @@ type HookTimeoutDebugContext = {
|
|
|
47
47
|
wait_point: string;
|
|
48
48
|
waiting_child_basename: string;
|
|
49
49
|
waiting_child_elapsed_ms: number;
|
|
50
|
-
load_average?: number | "unavailable";
|
|
50
|
+
load_average?: number | "unavailable" | "unsupported_platform";
|
|
51
51
|
spawn_ms?: number | "unavailable";
|
|
52
52
|
process_count?: number | "unavailable";
|
|
53
53
|
};
|
|
@@ -177,6 +177,7 @@ export const ALLOWED_SENTRY_REPORT_METADATA_KEYS = [
|
|
|
177
177
|
"final_elapsed_ms",
|
|
178
178
|
"bash_env_set",
|
|
179
179
|
"cwd_kind",
|
|
180
|
+
"root_storage_class",
|
|
180
181
|
"shell",
|
|
181
182
|
"nproc",
|
|
182
183
|
"hook_sequence",
|
|
@@ -290,6 +291,11 @@ function boundedDebugProbe(value) {
|
|
|
290
291
|
return value;
|
|
291
292
|
return boundedDebugMilliseconds(value) ?? undefined;
|
|
292
293
|
}
|
|
294
|
+
function boundedDebugLoadAverage(value) {
|
|
295
|
+
if (value === "unsupported_platform")
|
|
296
|
+
return value;
|
|
297
|
+
return boundedDebugProbe(value);
|
|
298
|
+
}
|
|
293
299
|
function boundedDebugPhaseTimings(value) {
|
|
294
300
|
if (!Array.isArray(value) || value.length > MAX_HOOK_TIMEOUT_DEBUG_PHASES)
|
|
295
301
|
return null;
|
|
@@ -324,7 +330,7 @@ function boundedHookTimeoutDebugContext(value) {
|
|
|
324
330
|
phaseTimings === null) {
|
|
325
331
|
return null;
|
|
326
332
|
}
|
|
327
|
-
const loadAverage =
|
|
333
|
+
const loadAverage = boundedDebugLoadAverage(value.load_average);
|
|
328
334
|
const spawnMs = boundedDebugProbe(value.spawn_ms);
|
|
329
335
|
const processCount = boundedDebugProbe(value.process_count);
|
|
330
336
|
const hasLoadAverage = typeof loadAverage === "number";
|
|
@@ -439,7 +445,11 @@ function parseSentryReportEvent(stdin) {
|
|
|
439
445
|
? boundedHookSequence(rawMetadata[key])
|
|
440
446
|
: key === "hook_timeout_debug_context"
|
|
441
447
|
? boundedHookTimeoutDebugContext(rawMetadata[key])
|
|
442
|
-
:
|
|
448
|
+
: key === "cwd_kind"
|
|
449
|
+
? closedMetadataValue(rawMetadata[key], SAFE_CWD_KINDS, "unknown")
|
|
450
|
+
: key === "root_storage_class"
|
|
451
|
+
? closedMetadataValue(rawMetadata[key], SAFE_ROOT_STORAGE_CLASSES, "unavailable")
|
|
452
|
+
: rawMetadata[key];
|
|
443
453
|
}
|
|
444
454
|
if (typeof metadata.slow_child === "string") {
|
|
445
455
|
metadata.slow_child = safeSlowChild(metadata);
|
|
@@ -484,6 +494,21 @@ const SAFE_HOOK_EVENT = /^[A-Za-z][A-Za-z0-9_.:-]{0,63}$/;
|
|
|
484
494
|
const SAFE_HOOK_SCRIPT = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
485
495
|
const SAFE_EXIT_CODE = /^(?:-?[0-9]+|running)$/;
|
|
486
496
|
const SAFE_NPROC = /^[0-9]+$/;
|
|
497
|
+
const SAFE_CWD_KINDS = new Set([
|
|
498
|
+
"hq-root",
|
|
499
|
+
"repo",
|
|
500
|
+
"worktree",
|
|
501
|
+
"hq_subdir",
|
|
502
|
+
"root_unset",
|
|
503
|
+
"root_not_directory",
|
|
504
|
+
"cwd_unset",
|
|
505
|
+
"cwd_outside_root",
|
|
506
|
+
]);
|
|
507
|
+
const SAFE_ROOT_STORAGE_CLASSES = new Set([
|
|
508
|
+
"system_drive",
|
|
509
|
+
"non_system_fixed_drive",
|
|
510
|
+
"network_or_mapped_drive",
|
|
511
|
+
]);
|
|
487
512
|
function safeTagValue(value, pattern = SAFE_TAG_TEXT) {
|
|
488
513
|
return pattern.test(value) ? value : "unknown";
|
|
489
514
|
}
|
|
@@ -546,14 +571,27 @@ function parsedLoadAverage(metadata) {
|
|
|
546
571
|
return value;
|
|
547
572
|
if (typeof value !== "string")
|
|
548
573
|
return undefined;
|
|
549
|
-
if (value === "unavailable")
|
|
550
|
-
return
|
|
574
|
+
if (value === "unavailable" || value === "unsupported_platform")
|
|
575
|
+
return value;
|
|
551
576
|
const match = /^\s*\{?\s*([0-9]+(?:\.[0-9]+)?)/.exec(value);
|
|
552
577
|
if (!match)
|
|
553
578
|
return undefined;
|
|
554
579
|
const parsed = Number(match[1]);
|
|
555
580
|
return Number.isFinite(parsed) ? parsed : undefined;
|
|
556
581
|
}
|
|
582
|
+
function cwdKindTag(metadata) {
|
|
583
|
+
const value = metadata.cwd_kind;
|
|
584
|
+
return typeof value === "string" && SAFE_CWD_KINDS.has(value) ? value : "unknown";
|
|
585
|
+
}
|
|
586
|
+
function rootStorageClassTag(metadata) {
|
|
587
|
+
const value = metadata.root_storage_class;
|
|
588
|
+
return typeof value === "string" && SAFE_ROOT_STORAGE_CLASSES.has(value)
|
|
589
|
+
? value
|
|
590
|
+
: "unavailable";
|
|
591
|
+
}
|
|
592
|
+
function closedMetadataValue(value, allowed, fallback) {
|
|
593
|
+
return typeof value === "string" && allowed.has(value) ? value : fallback;
|
|
594
|
+
}
|
|
557
595
|
function operatingSystemName(metadata) {
|
|
558
596
|
const platform = stringMetadata(metadata, "platform", "").toLowerCase();
|
|
559
597
|
if (/windows|win32|mingw|msys|cygwin/.test(platform))
|
|
@@ -608,8 +646,8 @@ function hookTimeoutDebugContextTags(context) {
|
|
|
608
646
|
timeout_debug_wait_point: context.wait_point,
|
|
609
647
|
timeout_debug_waiting_child: context.waiting_child_basename,
|
|
610
648
|
timeout_debug_waiting_child_elapsed_bucket: elapsedBucket(context.waiting_child_elapsed_ms),
|
|
611
|
-
timeout_debug_load_bucket: context.load_average === "
|
|
612
|
-
?
|
|
649
|
+
timeout_debug_load_bucket: typeof context.load_average === "string"
|
|
650
|
+
? context.load_average
|
|
613
651
|
: loadBucket(context.load_average),
|
|
614
652
|
timeout_debug_spawn_ms_bucket: debugSpawnMsBucket(context.spawn_ms),
|
|
615
653
|
timeout_debug_process_count_bucket: processCountBucket(context.process_count),
|
|
@@ -695,9 +733,10 @@ function hookTimeoutTags(event) {
|
|
|
695
733
|
hq_core_version: safeMetadataTag(metadata, "hq_version"),
|
|
696
734
|
shell: safeTagValue(shell),
|
|
697
735
|
bash_env: bashEnvTag(metadata),
|
|
698
|
-
cwd_kind:
|
|
736
|
+
cwd_kind: cwdKindTag(metadata),
|
|
699
737
|
os_name: operatingSystemName(metadata),
|
|
700
|
-
load_bucket: loadAverage === "
|
|
738
|
+
load_bucket: typeof loadAverage === "string" ? loadAverage : loadBucket(loadAverage),
|
|
739
|
+
root_storage_class: rootStorageClassTag(metadata),
|
|
701
740
|
spawn_ms_bucket: spawnMsBucket(metadata),
|
|
702
741
|
slow_child: safeSlowChild(metadata),
|
|
703
742
|
slow_phase: safeSlowPhase(metadata.slow_phase),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AuthState } from "./accounts.js";
|
|
2
2
|
import type { HostCapacityAccount, HostCapacitySnapshot } from "./host-capacity.js";
|
|
3
|
-
import type { LaneHost, LaneRecord, LanesConfig } from "./types.js";
|
|
3
|
+
import type { LaneHost, LanePlacementPolicy, LaneRecord, LanesConfig } from "./types.js";
|
|
4
4
|
import { type QuotaState } from "./quota.js";
|
|
5
5
|
export declare const BALANCE_LIVE_STATES: readonly ["running", "queued", "awaiting_input"];
|
|
6
6
|
export declare const BALANCE_MOVABLE_STATES: readonly ["interrupted", "done", "awaiting_input"];
|
|
@@ -81,6 +81,10 @@ export declare function selectClaudeAccount(input: {
|
|
|
81
81
|
recentUsageEnabled?: boolean;
|
|
82
82
|
now?: Date;
|
|
83
83
|
explicitAccount?: string;
|
|
84
|
+
requestedModelSource?: "flag" | "profile" | "lane" | "default";
|
|
85
|
+
requestedPlacement?: LanePlacementPolicy | null;
|
|
86
|
+
/** Actual host where the lane would run; targetHost may be a distinct balance host. */
|
|
87
|
+
actualTargetHost?: LaneHost;
|
|
84
88
|
unavailableHosts?: readonly BalanceHostGap[];
|
|
85
89
|
}): ClaudeAccountSelection;
|
|
86
90
|
export declare function accountLoadsForReport(input: {
|
|
@@ -119,6 +119,9 @@ function targetIneligibilityReason(accountId, globalEnabled, allowedAccounts, ta
|
|
|
119
119
|
return "login_status_unknown";
|
|
120
120
|
return undefined;
|
|
121
121
|
}
|
|
122
|
+
function configModelForAccount(config, accountId) {
|
|
123
|
+
return config.providers.claude.accounts[accountId]?.model;
|
|
124
|
+
}
|
|
122
125
|
function buildLoads(input) {
|
|
123
126
|
const configAccounts = input.config.providers.claude.accounts;
|
|
124
127
|
const targetHost = input.target.capacity.host;
|
|
@@ -156,10 +159,16 @@ function buildLoads(input) {
|
|
|
156
159
|
return { loads, skipped };
|
|
157
160
|
}
|
|
158
161
|
export function selectClaudeAccount(input) {
|
|
162
|
+
const actualTargetHost = input.actualTargetHost ?? input.targetHost;
|
|
163
|
+
const placementDetails = {
|
|
164
|
+
requested_placement: input.requestedPlacement ?? null,
|
|
165
|
+
target_host: actualTargetHost,
|
|
166
|
+
...(sameHost(actualTargetHost, input.targetHost) ? {} : { balance_host: input.targetHost }),
|
|
167
|
+
};
|
|
159
168
|
const target = input.hosts.find((host) => sameHost(host.capacity.host, input.targetHost));
|
|
160
169
|
if (!target) {
|
|
161
170
|
throw new LanesError("claude_account_balance_target_host_unavailable", `Cannot verify Claude accounts on target host ${input.targetHost.kind}:${input.targetHost.id}`, {
|
|
162
|
-
|
|
171
|
+
...placementDetails,
|
|
163
172
|
unavailable_hosts: input.unavailableHosts ?? [],
|
|
164
173
|
});
|
|
165
174
|
}
|
|
@@ -170,8 +179,16 @@ export function selectClaudeAccount(input) {
|
|
|
170
179
|
if (!selected || !selected.eligible) {
|
|
171
180
|
throw new LanesError("claude_account_balance_account_unavailable", `Explicit Claude account ${JSON.stringify(input.explicitAccount)} is unavailable on ${input.targetHost.kind}:${input.targetHost.id}`, {
|
|
172
181
|
account: input.explicitAccount,
|
|
173
|
-
target_host: input.targetHost,
|
|
174
182
|
reason: selected?.ineligible_reason ?? "unknown_account",
|
|
183
|
+
...placementDetails,
|
|
184
|
+
compared: {
|
|
185
|
+
requested_model: input.model ?? configModelForAccount(input.config, input.explicitAccount) ?? input.config.providers.claude.model ?? null,
|
|
186
|
+
requested_model_source: input.requestedModelSource ?? "default",
|
|
187
|
+
account_model: configModelForAccount(input.config, input.explicitAccount) ?? null,
|
|
188
|
+
account_model_source: `personal/lanes/config.json#providers.claude.accounts.${input.explicitAccount}.model`,
|
|
189
|
+
host: input.targetHost,
|
|
190
|
+
reason: selected?.ineligible_reason ?? "unknown_account",
|
|
191
|
+
},
|
|
175
192
|
account_reasons: skipped,
|
|
176
193
|
unavailable_hosts: input.unavailableHosts ?? [],
|
|
177
194
|
});
|
|
@@ -196,7 +213,7 @@ export function selectClaudeAccount(input) {
|
|
|
196
213
|
: "";
|
|
197
214
|
throw new LanesError("claude_account_balance_no_eligible_account", `No enabled, logged-in Claude account is available on ${input.targetHost.kind}:${input.targetHost.id}${unknownLoginDetail}`, {
|
|
198
215
|
provider: "claude",
|
|
199
|
-
|
|
216
|
+
...placementDetails,
|
|
200
217
|
account_reasons: loads.map((load) => ({
|
|
201
218
|
account: load.account,
|
|
202
219
|
reason: load.ineligible_reason ?? "unavailable",
|
package/dist/lib/lanes/spawn.js
CHANGED
|
@@ -334,11 +334,18 @@ function selectSpawnAccount(input) {
|
|
|
334
334
|
const selection = selectClaudeAccount({
|
|
335
335
|
hosts: [{ capacity, lanes, laneListAvailable: true }],
|
|
336
336
|
targetHost: lane.host,
|
|
337
|
+
actualTargetHost: lane.host,
|
|
338
|
+
requestedPlacement: null,
|
|
337
339
|
config,
|
|
338
340
|
quotaState: loadQuotaState(hqRoot),
|
|
339
341
|
...((recordedModel ?? pinnedModel ?? profileModel) !== undefined
|
|
340
342
|
? { model: recordedModel ?? pinnedModel ?? profileModel }
|
|
341
343
|
: {}),
|
|
344
|
+
requestedModelSource: recordedModel !== undefined || pinnedModel !== undefined
|
|
345
|
+
? "lane"
|
|
346
|
+
: profileModel !== undefined
|
|
347
|
+
? "profile"
|
|
348
|
+
: "default",
|
|
342
349
|
});
|
|
343
350
|
return {
|
|
344
351
|
account: resolveAccount(lane.provider, selection.account, config, context),
|