@indigoai-us/hq-cli 5.345.47 → 5.345.48
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 +5 -0
- 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 +4 -0
- package/dist/command-catalog.generated.js +5 -0
- 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/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/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/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/supervisor.d.ts +7 -1
- package/dist/lib/daemon/supervisor.js +50 -4
- package/dist/utils/resident-package-preload.js +4 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.345.48] — 2026-10-08
|
|
6
|
+
- Add default-off multi-company session locks with ordered `hq-session add/remove company` updates and scope-capability enforcement.
|
|
7
|
+
- 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.
|
|
8
|
+
- The desktop now uses the 60-second idle cadence when only outpost-hosted lanes need reconciliation.
|
|
9
|
+
|
|
5
10
|
## [5.345.47] — 2026-10-08
|
|
6
11
|
- `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
12
|
- 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.
|
|
@@ -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";
|
|
@@ -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",
|
|
@@ -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;
|