@indigoai-us/hq-cli 5.345.47 → 5.345.49
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 +15 -0
- package/README.md +2 -1
- package/assets/scaffold/core/scripts/hq-session.sh +113 -7
- package/assets/scaffold/core/scripts/lib/session-scope-capability.sh +101 -0
- package/dist/command-catalog.generated.d.ts +12 -0
- package/dist/command-catalog.generated.js +15 -0
- package/dist/commands/anywhere-runtime-flag.js +5 -3
- package/dist/commands/core-hq-session-native.d.ts +2 -0
- package/dist/commands/core-hq-session-native.js +242 -52
- package/dist/commands/core-resolve-company-native.d.ts +1 -0
- package/dist/commands/core-resolve-company-native.js +41 -11
- package/dist/commands/feedback.d.ts +10 -0
- package/dist/commands/feedback.js +45 -0
- package/dist/commands/lanes-message.js +2 -2
- package/dist/commands/lanes.js +2 -2
- package/dist/commands/mcp-serve.js +17 -4
- package/dist/commands/secrets.d.ts +5 -0
- package/dist/commands/secrets.js +181 -2
- package/dist/commands/workers.d.ts +4 -1
- package/dist/commands/workers.js +57 -15
- package/dist/lib/anywhere-person-setting.d.ts +1 -0
- package/dist/lib/anywhere-person-setting.js +12 -0
- package/dist/lib/anywhere-runtime-fast-flag.js +5 -3
- package/dist/lib/anywhere-runtime-flag-key.d.ts +3 -1
- package/dist/lib/anywhere-runtime-flag-key.js +6 -1
- package/dist/lib/cli-flag-keys.d.ts +1 -0
- package/dist/lib/cli-flag-keys.js +1 -0
- package/dist/lib/core-utils/session-id.d.ts +6 -0
- package/dist/lib/core-utils/session-id.js +35 -0
- package/dist/lib/daemon/catalog.js +1 -1
- package/dist/lib/daemon/flag.d.ts +1 -1
- package/dist/lib/daemon/flag.js +20 -9
- package/dist/lib/daemon/lanes-reconcile-guard.d.ts +6 -5
- package/dist/lib/daemon/lanes-reconcile-guard.js +26 -7
- package/dist/lib/daemon/run.d.ts +6 -0
- package/dist/lib/daemon/run.js +36 -9
- package/dist/lib/daemon/server.d.ts +1 -1
- package/dist/lib/daemon/supervisor.d.ts +7 -1
- package/dist/lib/daemon/supervisor.js +50 -4
- package/dist/lib/lanes/reconcile.js +6 -4
- package/dist/lib/lanes/registry-flag.d.ts +11 -1
- package/dist/lib/lanes/registry-flag.js +68 -29
- package/dist/lib/lanes/watcher.js +63 -2
- package/dist/utils/resident-package-preload.js +8 -1
- package/dist/utils/self-update.js +6 -5
- package/dist/utils/version-gate.d.ts +11 -4
- package/dist/utils/version-gate.js +132 -41
- package/docs/anywhere-runtime.md +5 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,21 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.49] — 2026-10-08
|
|
6
|
+
- Persist terminal lane envelopes and story metadata when a final report arrives after the lane has already stopped.
|
|
7
|
+
|
|
8
|
+
- Keep changelog guard coverage valid after release fragments move into CHANGELOG.md.
|
|
9
|
+
- The CLI now updates the pnpm-managed `hq` install that PATH resolves first, so `hq update` converges when another pnpm home shadows the running process.
|
|
10
|
+
- Preserve an explicit unknown state when the lanes flag registry cannot be read. The four message-delivery and session-wake gates now fail open on unknown; all other lanes gates retain their fail-closed behavior.
|
|
11
|
+
- `hq feedback bug` can include a fail-off, hq-flags-gated agent or human reporter mark derived from runtime context.
|
|
12
|
+
- HQ Anywhere remains enabled when its flag registry is unavailable, while the local kill switch still turns it off.
|
|
13
|
+
- Windows native-resolve cleanup accepts taskkill exit 128 only after confirming the target process has exited.
|
|
14
|
+
|
|
15
|
+
## [5.345.48] — 2026-10-08
|
|
16
|
+
- Add default-off multi-company session locks with ordered `hq-session add/remove company` updates and scope-capability enforcement.
|
|
17
|
+
- Give a sync runner that reports stopping 60 seconds to persist pending pushes before SIGKILL. The daemon shutdown deadline includes that grace; other services remain capped at 15 seconds.
|
|
18
|
+
- The desktop now uses the 60-second idle cadence when only outpost-hosted lanes need reconciliation.
|
|
19
|
+
|
|
5
20
|
## [5.345.47] — 2026-10-08
|
|
6
21
|
- `hq files share` now shows the server's reason when a private folder conflicts with its shared folder glob, for example that the glob still has grants or that only its creator or a company admin can replace it.
|
|
7
22
|
- After `hq install --global`, unauthenticated `claude` and `codex` commands show provider-specific sign-in instructions; Codex HTTP 401 transport text is kept in the HQ log.
|
package/README.md
CHANGED
|
@@ -9,7 +9,8 @@ hq --help
|
|
|
9
9
|
|
|
10
10
|
The global HQ runtime installs hooks for supported agent runtimes. When the
|
|
11
11
|
`hq-anywhere-runtime` flag is enabled, the hooks use hqd and a per-user folder
|
|
12
|
-
registry. The flag
|
|
12
|
+
registry. The flag defaults on when its registry row cannot be read, and an
|
|
13
|
+
explicit false turns it off. See [HQ Anywhere runtime](docs/anywhere-runtime.md)
|
|
13
14
|
for install and link commands.
|
|
14
15
|
|
|
15
16
|
## Live meeting transcripts
|
|
@@ -7,6 +7,9 @@
|
|
|
7
7
|
# core/scripts/hq-session.sh path # print path to current meta.yaml
|
|
8
8
|
# core/scripts/hq-session.sh get <key> # read a key from meta.yaml
|
|
9
9
|
# core/scripts/hq-session.sh set <key> <value> # set/replace a top-level key
|
|
10
|
+
# add company <slug> # add a company to the session lock set
|
|
11
|
+
# remove company <slug> # remove a company from the session lock set
|
|
12
|
+
# get company_slugs # print locked companies, primary first
|
|
10
13
|
#
|
|
11
14
|
# Reserved value:
|
|
12
15
|
# set company_slug personal # bind the session to no-company (personal) work
|
|
@@ -125,7 +128,7 @@ hq_session_has_case_alias() {
|
|
|
125
128
|
SESSION_ID_OVERRIDE=""
|
|
126
129
|
|
|
127
130
|
usage() {
|
|
128
|
-
sed -n '2,
|
|
131
|
+
sed -n '2,18p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
|
|
129
132
|
}
|
|
130
133
|
|
|
131
134
|
current_id() {
|
|
@@ -160,6 +163,12 @@ cmd_get() {
|
|
|
160
163
|
# Unreadable meta.yaml: awk fails and this function exits non-zero under
|
|
161
164
|
# set -e. Those two are not the same case — do not default either.
|
|
162
165
|
[ -n "$meta" ] && [ -f "$meta" ] || return 0
|
|
166
|
+
if [ "$key" = company_slugs ]; then
|
|
167
|
+
local lock_set primary
|
|
168
|
+
lock_set="$(awk '$1 == "company_slugs:" {sub(/^[^:]+:[[:space:]]*/, ""); gsub(/^"|"$/, ""); print; exit}' "$meta")"
|
|
169
|
+
if [ -n "$lock_set" ]; then printf '%s' "$lock_set"; else cmd_get company_slug; fi
|
|
170
|
+
return 0
|
|
171
|
+
fi
|
|
163
172
|
# Absent key: print nothing. For `senior`, a record written before that
|
|
164
173
|
# field existed has no line; that is a successful read of a file that does
|
|
165
174
|
# not contain the key, not a failed read. §3.1 defaults `senior` to `user`
|
|
@@ -179,6 +188,90 @@ cmd_get() {
|
|
|
179
188
|
' "$meta"
|
|
180
189
|
}
|
|
181
190
|
|
|
191
|
+
multi_company_enabled() {
|
|
192
|
+
local primary="${1:-}"
|
|
193
|
+
[ -n "$primary" ] || primary="$(cmd_get company_slug)"
|
|
194
|
+
session_scope_multi_company_enabled "$REPO_ROOT" "$primary"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
validate_company_slug() {
|
|
198
|
+
local slug="${1:-}"
|
|
199
|
+
case "$slug" in ''|*[!a-zA-Z0-9_-]*) echo "hq-session: invalid company_slug: '$slug' (allowed characters: A-Z a-z 0-9 - _)" >&2; return 1 ;; esac
|
|
200
|
+
[ "$slug" != personal ] || { echo "hq-session: personal cannot be combined with a company" >&2; return 1; }
|
|
201
|
+
if [ -L "$REPO_ROOT/companies/$slug" ] || ! hq_session_has_exact_company_dir "$REPO_ROOT" "$slug" || hq_session_has_case_alias "$REPO_ROOT" "$slug"; then
|
|
202
|
+
echo "hq-session: company_slug must use the exact real directory name: '$slug'" >&2
|
|
203
|
+
return 1
|
|
204
|
+
fi
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
write_company_lock_set() {
|
|
208
|
+
local meta="$1" set="$2" primary="$3" tmp
|
|
209
|
+
tmp="$(mktemp)"
|
|
210
|
+
awk -v primary="$primary" -v slugs="$set" '
|
|
211
|
+
BEGIN { has_primary=0; has_slugs=0 }
|
|
212
|
+
$1 == "company_slug:" { if (!has_primary) print "company_slug: " primary; has_primary=1; next }
|
|
213
|
+
$1 == "company_slugs:" { if (!has_slugs) print "company_slugs: " slugs; has_slugs=1; next }
|
|
214
|
+
{ print }
|
|
215
|
+
END { if (!has_primary) print "company_slug: " primary; if (!has_slugs) print "company_slugs: " slugs }
|
|
216
|
+
' "$meta" >"$tmp"
|
|
217
|
+
mv "$tmp" "$meta"
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
emit_lock_set_context() {
|
|
221
|
+
local sid="$1" slugs="$2" slug
|
|
222
|
+
IFS=, read -r -a __session_slugs <<< "$slugs"
|
|
223
|
+
for slug in "${__session_slugs[@]}"; do
|
|
224
|
+
[ "$slug" = personal ] && continue
|
|
225
|
+
emit_company_hard_policies "$slug"
|
|
226
|
+
done
|
|
227
|
+
spawn_work_mesh_register "$sid" || true
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
cmd_add_company() {
|
|
231
|
+
local slug="${1:-}" id meta current slugs next
|
|
232
|
+
[ -n "$slug" ] || { echo "usage: hq-session.sh add company <slug>" >&2; exit 1; }
|
|
233
|
+
if ! multi_company_enabled; then
|
|
234
|
+
echo "hq-session: add company is disabled; hooks.multi-company-session-lock is off" >&2
|
|
235
|
+
exit 1
|
|
236
|
+
fi
|
|
237
|
+
validate_company_slug "$slug" || exit 1
|
|
238
|
+
id="$(current_id)"
|
|
239
|
+
[ -n "$id" ] || { echo "hq-session: no current session" >&2; exit 1; }
|
|
240
|
+
meta="$SESSIONS_DIR/$id/meta.yaml"
|
|
241
|
+
[ -f "$meta" ] || { echo "hq-session: session is not company-bound" >&2; exit 1; }
|
|
242
|
+
current="$(cmd_get company_slug)"
|
|
243
|
+
[ -n "$current" ] || { echo "hq-session: session is not company-bound" >&2; exit 1; }
|
|
244
|
+
slugs="$(cmd_get company_slugs)"
|
|
245
|
+
if [ "$current" = personal ]; then next="$slug"; else
|
|
246
|
+
case ",$slugs," in *",$slug,"*) next="$slugs" ;; *) next="$slugs,$slug" ;; esac
|
|
247
|
+
fi
|
|
248
|
+
write_company_lock_set "$meta" "$next" "${next%%,*}"
|
|
249
|
+
session_scope_mint_set "$REPO_ROOT" "$id" "$next" || { echo "hq-session: failed to mint scope-capability for session $id" >&2; exit 1; }
|
|
250
|
+
emit_lock_set_context "$id" "$next"
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
cmd_remove_company() {
|
|
254
|
+
local slug="${1:-}" id meta current slugs next out entry removed=0
|
|
255
|
+
[ -n "$slug" ] || { echo "usage: hq-session.sh remove company <slug>" >&2; exit 1; }
|
|
256
|
+
id="$(current_id)"
|
|
257
|
+
[ -n "$id" ] || { echo "hq-session: no current session" >&2; exit 1; }
|
|
258
|
+
meta="$SESSIONS_DIR/$id/meta.yaml"
|
|
259
|
+
current="$(cmd_get company_slug)"
|
|
260
|
+
slugs="$(cmd_get company_slugs)"
|
|
261
|
+
[ -n "$slugs" ] || { echo "hq-session: session has no company lock" >&2; exit 1; }
|
|
262
|
+
out=""
|
|
263
|
+
IFS=, read -r -a __session_slugs <<< "$slugs"
|
|
264
|
+
for entry in "${__session_slugs[@]}"; do
|
|
265
|
+
if [ "$entry" = "$slug" ]; then removed=1; else out="${out:+$out,}$entry"; fi
|
|
266
|
+
done
|
|
267
|
+
[ "$removed" = 1 ] || { echo "hq-session: company is not locked: $slug" >&2; exit 1; }
|
|
268
|
+
[ -n "$out" ] || { echo "hq-session: cannot remove the last company; use set company_slug" >&2; exit 1; }
|
|
269
|
+
next="$out"
|
|
270
|
+
write_company_lock_set "$meta" "$next" "${next%%,*}"
|
|
271
|
+
session_scope_mint_set "$REPO_ROOT" "$id" "$next" || { echo "hq-session: failed to mint scope-capability for session $id" >&2; exit 1; }
|
|
272
|
+
emit_lock_set_context "$id" "$next"
|
|
273
|
+
}
|
|
274
|
+
|
|
182
275
|
cmd_set() {
|
|
183
276
|
local key="${1:-}" value="${2:-}"
|
|
184
277
|
[ -n "$key" ] && [ $# -ge 2 ] || { echo "usage: hq-session.sh set <key> <value>" >&2; exit 1; }
|
|
@@ -254,15 +347,26 @@ cmd_set() {
|
|
|
254
347
|
local prev=""
|
|
255
348
|
prev="$(cmd_get "$key" 2>/dev/null || true)"
|
|
256
349
|
|
|
350
|
+
# A company switch invalidates the old project/task; clear both in the same
|
|
351
|
+
# metadata rewrite before the company bind can register with Work Mesh.
|
|
352
|
+
local clear_project_task=0
|
|
353
|
+
if [ "$key" = "company_slug" ] && [ -n "$prev" ] && [ "$value" != "$prev" ]; then
|
|
354
|
+
clear_project_task=1
|
|
355
|
+
fi
|
|
356
|
+
|
|
257
357
|
local tmp
|
|
258
358
|
tmp="$(mktemp)"
|
|
259
|
-
awk -v k="$key" -v v="$value" '
|
|
359
|
+
awk -v clear_project_task="$clear_project_task" -v k="$key" -v v="$value" '
|
|
260
360
|
BEGIN { found = 0 }
|
|
361
|
+
clear_project_task && ($1 == "project:" || $1 == "task:") { next }
|
|
261
362
|
$1 == k":" { print k": " v; found = 1; next }
|
|
262
363
|
{ print }
|
|
263
364
|
END { if (!found) print k": " v }
|
|
264
365
|
' "$meta" > "$tmp"
|
|
265
366
|
mv "$tmp" "$meta"
|
|
367
|
+
if [ "$key" = company_slug ]; then
|
|
368
|
+
write_company_lock_set "$meta" "$value" "$value"
|
|
369
|
+
fi
|
|
266
370
|
|
|
267
371
|
# When a company is bound (or rebound) mid-session, surface that company's
|
|
268
372
|
# hard-enforcement policies into this Bash tool result. SessionStart only
|
|
@@ -271,7 +375,7 @@ cmd_set() {
|
|
|
271
375
|
# do company infra/deploy/credential work blind to hard rules. This closes
|
|
272
376
|
# that gap. Emits policy text only — never secrets.
|
|
273
377
|
if [ "$key" = "company_slug" ] && [ -n "$value" ] && [ "$value" != "$prev" ]; then
|
|
274
|
-
|
|
378
|
+
session_scope_mint_set "$REPO_ROOT" "$id" "$value" || {
|
|
275
379
|
echo "hq-session: failed to mint scope-capability for session $id" >&2
|
|
276
380
|
exit 1
|
|
277
381
|
}
|
|
@@ -285,7 +389,7 @@ cmd_set() {
|
|
|
285
389
|
# Fire-and-forget: register this company bind with the Work Mesh (US-003).
|
|
286
390
|
# Fully silent (all output → the hook's own bounded log) and guarded so it
|
|
287
391
|
# can neither fail cmd_set under `set -euo pipefail` nor delay its return.
|
|
288
|
-
spawn_work_mesh_register "$id" || true
|
|
392
|
+
spawn_work_mesh_register "$id" "$value" || true
|
|
289
393
|
fi
|
|
290
394
|
fi
|
|
291
395
|
|
|
@@ -318,13 +422,13 @@ cmd_set() {
|
|
|
318
422
|
# tests copy only hq-session.sh). Never blocks: the hook itself is fire-and-forget
|
|
319
423
|
# and this backgrounds even its fast foreground path off cmd_set's return path.
|
|
320
424
|
spawn_work_mesh_register() {
|
|
321
|
-
local sid="$1"
|
|
425
|
+
local sid="$1" company="${2:-}"
|
|
322
426
|
local hook="$REPO_ROOT/core/hooks/SessionStart/35-work-mesh-session-start.sh"
|
|
323
427
|
local logf="$REPO_ROOT/workspace/logs/work-mesh-hook.log"
|
|
324
428
|
[ -f "$hook" ] || return 0
|
|
325
429
|
command -v jq >/dev/null 2>&1 || return 0
|
|
326
430
|
local ev
|
|
327
|
-
ev="$(jq -nc --arg sid "$sid" --arg cwd "${PWD:-}" '{session_id:$sid,cwd:$cwd}' 2>/dev/null)" || return 0
|
|
431
|
+
ev="$(jq -nc --arg sid "$sid" --arg company "$company" --arg cwd "${PWD:-}" '{session_id:$sid,cwd:$cwd} + (if $company == "" then {} else {company_slug:$company} end)' 2>/dev/null)" || return 0
|
|
328
432
|
[ -n "$ev" ] || return 0
|
|
329
433
|
mkdir -p "$(dirname "$logf")" 2>/dev/null || true
|
|
330
434
|
HQ_ROOT="$REPO_ROOT" CLAUDE_CODE_SESSION_ID="$sid" \
|
|
@@ -373,7 +477,7 @@ emit_company_hard_policies() {
|
|
|
373
477
|
/^---[ \t]*$/ { d++; next }
|
|
374
478
|
d==1 && /^id:/ { s=$0; sub(/^id:[ \t]*/,"",s); gsub(/^["'"'"']|["'"'"']$/,"",s); id=s; next }
|
|
375
479
|
d==1 && /^enforcement:/ { s=$0; sub(/^enforcement:[ \t]*/,"",s); gsub(/[ \t]/,"",s); enf=s; next }
|
|
376
|
-
d==1 && /^status:[ \t]*retired/ { retired=1; next }
|
|
480
|
+
d==1 && /^status:[ \t]*retired/ { retired=1 ; next }
|
|
377
481
|
d>=2 && /^## Rule[ \t]*$/ { rsec=1; next }
|
|
378
482
|
d>=2 && rsec && /^## / { rsec=0 }
|
|
379
483
|
d>=2 && rsec && !rcap && NF { line=$0; gsub(/\*\*/,"",line); if(length(line)>160)line=substr(line,1,157)"..."; rule=line; rcap=1 }
|
|
@@ -438,6 +542,8 @@ case "$sub" in
|
|
|
438
542
|
path) cmd_path "$@" ;;
|
|
439
543
|
get) cmd_get "$@" ;;
|
|
440
544
|
set) cmd_set "$@" ;;
|
|
545
|
+
add) [ "${1:-}" = company ] || { echo "usage: hq-session.sh add company <slug>" >&2; exit 1; }; shift; cmd_add_company "$@" ;;
|
|
546
|
+
remove) [ "${1:-}" = company ] || { echo "usage: hq-session.sh remove company <slug>" >&2; exit 1; }; shift; cmd_remove_company "$@" ;;
|
|
441
547
|
""|-h|--help|help) usage ;;
|
|
442
548
|
*) echo "hq-session: unknown subcommand '$sub'" >&2; usage >&2; exit 1 ;;
|
|
443
549
|
esac
|
|
@@ -5,6 +5,24 @@
|
|
|
5
5
|
# Sourced by core/scripts/hq-session.sh and .claude/hooks/mandatory-scope-authorizer.sh.
|
|
6
6
|
# Never execute directly.
|
|
7
7
|
|
|
8
|
+
session_scope_multi_company_enabled() {
|
|
9
|
+
local root="${1:-}" primary="${2:-}" flag_script
|
|
10
|
+
flag_script="$root/.codex/hooks/codex-explicit-path-flag.cjs"
|
|
11
|
+
if ! command -v hqd_hook_flag_enabled_for >/dev/null 2>&1 && [ -f "$root/core/scripts/hqd-hook-flag-cache-lib.sh" ]; then
|
|
12
|
+
# shellcheck source=../hqd-hook-flag-cache-lib.sh
|
|
13
|
+
. "$root/core/scripts/hqd-hook-flag-cache-lib.sh"
|
|
14
|
+
fi
|
|
15
|
+
if command -v hqd_hook_flag_enabled_for >/dev/null 2>&1; then
|
|
16
|
+
hqd_hook_flag_enabled_for multi-company-session-lock hooks.multi-company-session-lock \
|
|
17
|
+
"$flag_script" "$root" "$primary"
|
|
18
|
+
[ "${HQD_FLAG_ENABLED:-false}" = true ]
|
|
19
|
+
return $?
|
|
20
|
+
fi
|
|
21
|
+
# The cache library is part of the live HQ tree. Missing it is a lookup error,
|
|
22
|
+
# so keep the gate off instead of introducing uncached Node work on a hook.
|
|
23
|
+
return 1
|
|
24
|
+
}
|
|
25
|
+
|
|
8
26
|
# session_scope_identity_is_valid <identity>
|
|
9
27
|
# Identities become path segments; accept only a conservative portable set.
|
|
10
28
|
session_scope_identity_is_valid() {
|
|
@@ -40,6 +58,55 @@ session_scope_read() {
|
|
|
40
58
|
"$cap" 2>/dev/null || true
|
|
41
59
|
}
|
|
42
60
|
|
|
61
|
+
# session_scope_read_companies <root> <session_id> [agent_id]
|
|
62
|
+
# Print the ordered, validated lock set. Legacy capabilities remain singleton
|
|
63
|
+
# locks; malformed company_slugs fail closed instead of falling back to primary.
|
|
64
|
+
session_scope_read_companies() {
|
|
65
|
+
local root="${1:-}" sid="${2:-}" aid="${3:-}" cap lock_info primary has_set result slug out=""
|
|
66
|
+
local -a slugs
|
|
67
|
+
cap="$(session_scope_capability_path "$root" "$sid" "$aid")" || return 0
|
|
68
|
+
[ -f "$cap" ] || return 0
|
|
69
|
+
lock_info="$(jq -er --arg sid "$sid" --arg aid "$aid" '
|
|
70
|
+
select(.session_id == $sid and (.agent_id // "") == $aid)
|
|
71
|
+
| if (.company_slug | type) != "string" or (.company_slug | test("^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$") | not) then error("invalid primary")
|
|
72
|
+
elif has("company_slugs") then
|
|
73
|
+
if (.company_slugs | type) == "array" and all(.company_slugs[]; type == "string" and test("^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$"))
|
|
74
|
+
and (.company_slugs | length) > 0 and .company_slugs[0] == .company_slug
|
|
75
|
+
and ((.company_slugs | unique | length) == (.company_slugs | length))
|
|
76
|
+
and ((.company_slug == "personal" and (.company_slugs | length) == 1)
|
|
77
|
+
or (.company_slugs | all(. != "personal"))) then
|
|
78
|
+
[.company_slug, "true", (.company_slugs | join(","))] | @tsv
|
|
79
|
+
else error("invalid company_slugs") end
|
|
80
|
+
else [.company_slug, "false", .company_slug] | @tsv end
|
|
81
|
+
' "$cap" 2>/dev/null)" || return 0
|
|
82
|
+
IFS="$(printf '\t')" read -r primary has_set result <<EOF
|
|
83
|
+
$lock_info
|
|
84
|
+
EOF
|
|
85
|
+
case "$primary" in ''|*[!A-Za-z0-9_-]*) return 0 ;; esac
|
|
86
|
+
IFS=, read -r -a slugs <<< "$result"
|
|
87
|
+
# A singleton is identical whether the gate is on or off; skip the flag
|
|
88
|
+
# client on the session-start hot path.
|
|
89
|
+
if [ "${#slugs[@]}" -le 1 ]; then
|
|
90
|
+
if [ "$primary" = personal ] || { [ -d "$root/companies/$primary" ] && [ ! -L "$root/companies/$primary" ]; }; then
|
|
91
|
+
printf '%s\n' "$primary"
|
|
92
|
+
fi
|
|
93
|
+
return 0
|
|
94
|
+
fi
|
|
95
|
+
if [ "$has_set" != true ] || ! session_scope_multi_company_enabled "$root" "$primary"; then
|
|
96
|
+
if [ "$primary" = personal ] || { [ -d "$root/companies/$primary" ] && [ ! -L "$root/companies/$primary" ]; }; then
|
|
97
|
+
printf '%s\n' "$primary"
|
|
98
|
+
fi
|
|
99
|
+
return 0
|
|
100
|
+
fi
|
|
101
|
+
for slug in "${slugs[@]}"; do
|
|
102
|
+
case "$slug" in ''|*[!a-zA-Z0-9_-]*) return 0 ;; esac
|
|
103
|
+
if [ "$slug" = personal ] || { [ -d "$root/companies/$slug" ] && [ ! -L "$root/companies/$slug" ]; }; then
|
|
104
|
+
case ",$out," in *",$slug,"*) ;; *) out="${out:+$out,}$slug" ;; esac
|
|
105
|
+
fi
|
|
106
|
+
done
|
|
107
|
+
[ -n "$out" ] && printf '%s\n' "$out" | tr ',' '\n'
|
|
108
|
+
}
|
|
109
|
+
|
|
43
110
|
# session_scope_resolve_agent_company <root> <session_id> <agent_id> <session_company> <event>
|
|
44
111
|
# Existing tuple bindings always win. Session metadata may bind a caller only
|
|
45
112
|
# on SessionStart, when that agent has not been bound before.
|
|
@@ -53,6 +120,16 @@ session_scope_resolve_agent_company() {
|
|
|
53
120
|
fi
|
|
54
121
|
}
|
|
55
122
|
|
|
123
|
+
session_scope_resolve_agent_companies() {
|
|
124
|
+
local root="${1:-}" sid="${2:-}" aid="${3:-}" session_companies="${4:-}" event="${5:-}" bound=""
|
|
125
|
+
bound="$(session_scope_read_companies "$root" "$sid" "$aid" 2>/dev/null || true)"
|
|
126
|
+
if [ -n "$bound" ]; then
|
|
127
|
+
printf '%s\n' "$bound"
|
|
128
|
+
elif [ "$event" = "SessionStart" ] && [ -n "$session_companies" ]; then
|
|
129
|
+
printf '%s\n' "$session_companies" | tr ',' '\n' | awk 'NF && !seen[$0]++'
|
|
130
|
+
fi
|
|
131
|
+
}
|
|
132
|
+
|
|
56
133
|
# session_scope_mint <root> <session_id> <company_slug> [agent_id]
|
|
57
134
|
# Write scope-capability.json for the exact caller. Main-thread writes retain
|
|
58
135
|
# the legacy shape/path for one-restart compatibility.
|
|
@@ -89,3 +166,27 @@ session_scope_mint() {
|
|
|
89
166
|
|| { rm -f "$tmp"; return 1; }
|
|
90
167
|
mv "$tmp" "$cap"
|
|
91
168
|
}
|
|
169
|
+
|
|
170
|
+
# session_scope_mint_set <root> <session_id> <comma-separated-slugs> [agent_id]
|
|
171
|
+
session_scope_mint_set() {
|
|
172
|
+
local root="${1:-}" sid="${2:-}" slugs="${3:-}" aid="${4:-}" first rest slug json
|
|
173
|
+
[ -n "$slugs" ] || return 1
|
|
174
|
+
first="${slugs%%,*}"
|
|
175
|
+
case "$first" in ''|*[!a-zA-Z0-9_-]*) return 1 ;; esac
|
|
176
|
+
rest="${slugs#"$first"}"
|
|
177
|
+
while [ -n "$rest" ]; do
|
|
178
|
+
case "$rest" in ,*) rest="${rest#,}" ;; *) return 1 ;; esac
|
|
179
|
+
slug="${rest%%,*}"
|
|
180
|
+
case "$slug" in ''|*[!a-zA-Z0-9_-]*) return 1 ;; esac
|
|
181
|
+
rest="${rest#"$slug"}"
|
|
182
|
+
[ "$rest" = "$slug" ] && break
|
|
183
|
+
done
|
|
184
|
+
json="$(jq -nc --arg slugs "$slugs" '$slugs | split(",") | reduce .[] as $slug ([]; if index($slug) == null then . + [$slug] else . end)')" || return 1
|
|
185
|
+
session_scope_mint "$root" "$sid" "$first" "$aid" || return 1
|
|
186
|
+
local cap tmp minted_at
|
|
187
|
+
cap="$(session_scope_capability_path "$root" "$sid" "$aid")" || return 1
|
|
188
|
+
minted_at="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
|
|
189
|
+
tmp="$(mktemp)"
|
|
190
|
+
jq --argjson slugs "$json" --arg minted_at "$minted_at" '.company_slugs=$slugs | .minted_at=$minted_at' "$cap" >"$tmp" || { rm -f "$tmp"; return 1; }
|
|
191
|
+
mv "$tmp" "$cap"
|
|
192
|
+
}
|
|
@@ -1723,6 +1723,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
1723
1723
|
readonly flags: "--only <keys>";
|
|
1724
1724
|
readonly description: "Secret names to inject (comma-separated; may be repeated) (required)";
|
|
1725
1725
|
readonly hidden: false;
|
|
1726
|
+
}, {
|
|
1727
|
+
readonly flags: "--company <slug>";
|
|
1728
|
+
readonly description: "Company slug (resolves to companyUid)";
|
|
1729
|
+
readonly hidden: false;
|
|
1726
1730
|
}, {
|
|
1727
1731
|
readonly flags: "--script <path>";
|
|
1728
1732
|
readonly description: "Attach local script identity for script-locked secrets";
|
|
@@ -3669,6 +3673,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3669
3673
|
readonly flags: "--company <slug>";
|
|
3670
3674
|
readonly description: "Company slug to associate with the report";
|
|
3671
3675
|
readonly hidden: false;
|
|
3676
|
+
}, {
|
|
3677
|
+
readonly flags: "--reporter <kind>";
|
|
3678
|
+
readonly description: "Mark this bug report as agent-filed (only 'agent' is accepted)";
|
|
3679
|
+
readonly hidden: false;
|
|
3672
3680
|
}, {
|
|
3673
3681
|
readonly flags: "--screenshot <path>";
|
|
3674
3682
|
readonly description: "Attach a screenshot (repeatable, up to 5; .png/.jpg/.jpeg/.webp/.gif)";
|
|
@@ -3702,6 +3710,10 @@ export declare const COMMAND_CATALOG: readonly [{
|
|
|
3702
3710
|
readonly flags: "--company <slug>";
|
|
3703
3711
|
readonly description: "Company slug to associate with the report";
|
|
3704
3712
|
readonly hidden: false;
|
|
3713
|
+
}, {
|
|
3714
|
+
readonly flags: "--reporter <kind>";
|
|
3715
|
+
readonly description: "Mark this bug report as agent-filed (only 'agent' is accepted)";
|
|
3716
|
+
readonly hidden: false;
|
|
3705
3717
|
}, {
|
|
3706
3718
|
readonly flags: "--screenshot <path>";
|
|
3707
3719
|
readonly description: "Attach a screenshot (repeatable, up to 5; .png/.jpg/.jpeg/.webp/.gif)";
|
|
@@ -2065,6 +2065,11 @@ export const COMMAND_CATALOG = [
|
|
|
2065
2065
|
"description": "Secret names to inject (comma-separated; may be repeated) (required)",
|
|
2066
2066
|
"hidden": false
|
|
2067
2067
|
},
|
|
2068
|
+
{
|
|
2069
|
+
"flags": "--company <slug>",
|
|
2070
|
+
"description": "Company slug (resolves to companyUid)",
|
|
2071
|
+
"hidden": false
|
|
2072
|
+
},
|
|
2068
2073
|
{
|
|
2069
2074
|
"flags": "--script <path>",
|
|
2070
2075
|
"description": "Attach local script identity for script-locked secrets",
|
|
@@ -4437,6 +4442,11 @@ export const COMMAND_CATALOG = [
|
|
|
4437
4442
|
"description": "Company slug to associate with the report",
|
|
4438
4443
|
"hidden": false
|
|
4439
4444
|
},
|
|
4445
|
+
{
|
|
4446
|
+
"flags": "--reporter <kind>",
|
|
4447
|
+
"description": "Mark this bug report as agent-filed (only 'agent' is accepted)",
|
|
4448
|
+
"hidden": false
|
|
4449
|
+
},
|
|
4440
4450
|
{
|
|
4441
4451
|
"flags": "--screenshot <path>",
|
|
4442
4452
|
"description": "Attach a screenshot (repeatable, up to 5; .png/.jpg/.jpeg/.webp/.gif)",
|
|
@@ -4477,6 +4487,11 @@ export const COMMAND_CATALOG = [
|
|
|
4477
4487
|
"description": "Company slug to associate with the report",
|
|
4478
4488
|
"hidden": false
|
|
4479
4489
|
},
|
|
4490
|
+
{
|
|
4491
|
+
"flags": "--reporter <kind>",
|
|
4492
|
+
"description": "Mark this bug report as agent-filed (only 'agent' is accepted)",
|
|
4493
|
+
"hidden": false
|
|
4494
|
+
},
|
|
4480
4495
|
{
|
|
4481
4496
|
"flags": "--screenshot <path>",
|
|
4482
4497
|
"description": "Attach a screenshot (repeatable, up to 5; .png/.jpg/.jpeg/.webp/.gif)",
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { resolveFlagGate, resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
2
|
-
import { HQ_ANYWHERE_RUNTIME_FLAG } from "../lib/anywhere-runtime-flag-key.js";
|
|
2
|
+
import { HQ_ANYWHERE_RUNTIME_FLAG, hqAnywhereRuntimeLocallyDisabled } from "../lib/anywhere-runtime-flag-key.js";
|
|
3
3
|
export { HQ_ANYWHERE_RUNTIME_FLAG };
|
|
4
4
|
export class AnywhereRuntimeDisabledError extends Error {
|
|
5
5
|
expected = true;
|
|
@@ -9,8 +9,10 @@ export class AnywhereRuntimeDisabledError extends Error {
|
|
|
9
9
|
}
|
|
10
10
|
}
|
|
11
11
|
export function anywhereRuntimeEnabled(env = process.env) {
|
|
12
|
-
|
|
13
|
-
|
|
12
|
+
if (hqAnywhereRuntimeLocallyDisabled(env))
|
|
13
|
+
return false;
|
|
14
|
+
const fallback = () => true;
|
|
15
|
+
const lookup = { fallback: true };
|
|
14
16
|
return env === process.env
|
|
15
17
|
? resolveProcessFlagGate(HQ_ANYWHERE_RUNTIME_FLAG, lookup, fallback)
|
|
16
18
|
: resolveFlagGate(undefined, HQ_ANYWHERE_RUNTIME_FLAG, lookup, fallback, env);
|
|
@@ -28,7 +28,9 @@ export type HqSessionDependencies = {
|
|
|
28
28
|
stderr: string;
|
|
29
29
|
error?: Error;
|
|
30
30
|
};
|
|
31
|
+
multiCompanyLockEnabled: boolean;
|
|
31
32
|
};
|
|
33
|
+
export { validSessionId } from "../lib/core-utils/session-id.js";
|
|
32
34
|
/** In-process command body, with injectable file access for parity tests. */
|
|
33
35
|
export declare function executeHqSession(argv: readonly string[], context: {
|
|
34
36
|
cwd: string;
|