@indigoai-us/hq-cli 5.311.1 → 5.311.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +12 -0
- package/assets/scaffold/core/scripts/hq-delegate-grant.sh +31 -5
- package/assets/scaffold/core/scripts/hq-delegate-send.sh +1 -1
- package/assets/scaffold/core/scripts/hq-delegate-verify.sh +27 -2
- package/dist/commands/core-delegate-w1b.js +54 -7
- package/dist/commands/core-delegate.d.ts +1 -0
- package/dist/commands/core-delegate.js +1 -1
- package/dist/commands/files-browse.d.ts +3 -2
- package/dist/commands/files-browse.js +2 -7
- package/dist/commands/lanes-message.js +18 -7
- package/dist/commands/lanes-shared.d.ts +2 -0
- package/dist/commands/lanes-watch.js +1 -5
- package/dist/commands/lanes.js +10 -13
- package/dist/fast-scaffold.d.ts +4 -0
- package/dist/fast-scaffold.js +5 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/lib/flag-registry.d.ts +2 -0
- package/dist/lib/flag-registry.js +19 -12
- package/dist/lib/lanes/autopilot.js +19 -2
- package/dist/lib/lanes/delivery.d.ts +1 -0
- package/dist/lib/lanes/keep-alive.d.ts +1 -3
- package/dist/lib/lanes/keep-alive.js +2 -4
- package/dist/lib/lanes/reconcile.js +14 -36
- package/dist/lib/lanes/spawn.d.ts +2 -0
- package/dist/lib/lanes/spawn.js +35 -12
- package/dist/lib/lanes/types.d.ts +2 -2
- package/dist/lib/lanes/watch.d.ts +0 -1
- package/dist/lib/lanes/watch.js +4 -9
- package/dist/main.js +1 -1
- package/dist/node-network-compat-core.d.ts +12 -0
- package/dist/node-network-compat-core.js +41 -0
- package/dist/node-network-compat-fast.d.ts +7 -0
- package/dist/node-network-compat-fast.js +23 -0
- package/dist/node-network-compat.d.ts +2 -18
- package/dist/node-network-compat.js +4 -39
- package/dist/node-preflight.d.ts +7 -1
- package/dist/node-preflight.js +5 -5
- package/dist/utils/files-browse-acl.d.ts +5 -0
- package/dist/utils/files-browse-acl.js +11 -0
- package/dist/utils/hq-core-yaml.d.ts +10 -0
- package/dist/utils/hq-core-yaml.js +40 -0
- package/dist/utils/pack-contributions.d.ts +2 -9
- package/dist/utils/pack-contributions.js +1 -39
- package/dist/utils/scaffold-fast-import-guard.js +5 -1
- package/dist/utils/version-report.js +1 -1
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [5.311.3] — 2026-10-01
|
|
6
|
+
- Match company file access checks against the company-anchored paths printed by `hq files browse`.
|
|
7
|
+
- Record a placement resolved to the current host as local so the lane starts without dispatching back through Outpost pickup.
|
|
8
|
+
|
|
9
|
+
## [5.311.2] — 2026-10-01
|
|
10
|
+
- Reduce fast-path startup work by loading network compatibility only on routes that need it.
|
|
11
|
+
- Make envelope `next_check_at` an unconditional keep-alive hold and remove its completed rollout flag.
|
|
12
|
+
- Lead autopilot no longer asks a lead to review its own lane as a finished worker, or to relaunch a worker for a story assigned to the lead itself.
|
|
13
|
+
- Lead autopilot counts a story's current `lane` together with its `lanes` history, so `lanes: []` no longer hides the assigned lane.
|
|
14
|
+
- Keep the CLI company seed fixture byte-matched to the hq-core starter golden, including the session sentinel ignore rule.
|
|
15
|
+
- Record lane-watcher continuations as `watcher` resumes and label their prompt source accurately; record monitor-event and system-notice message sources distinctly in lane resume history.
|
|
16
|
+
|
|
5
17
|
## [5.311.1] — 2026-10-01
|
|
6
18
|
- Preserve shell-compatible diagnostics when delegate pickup receives a malformed manifest.
|
|
7
19
|
- Print URLs returned by `hq integrations call` without redacting their paths or query values. Other integration JSON output keeps its existing URL redaction.
|
|
@@ -60,13 +60,31 @@ STATUS="$(jq -r '.status // empty' "$MANIFEST")"
|
|
|
60
60
|
|
|
61
61
|
# File-ACL principals must be an email, grp_<id>, or @all — an agentUid is
|
|
62
62
|
# NOT accepted by `hq files share`. For agent recipients, grants flow through
|
|
63
|
-
# a deterministic per-agent delegation group
|
|
64
|
-
# grp_dlg
|
|
63
|
+
# a deterministic per-agent delegation group keyed by the full UID digest:
|
|
64
|
+
# grp_dlg<first 32 SHA-256 hex characters>, containing exactly that agent.
|
|
65
|
+
agent_group_id() {
|
|
66
|
+
local principal="$1" digest
|
|
67
|
+
if command -v sha256sum >/dev/null 2>&1; then
|
|
68
|
+
digest="$(printf '%s' "$principal" | sha256sum)"
|
|
69
|
+
elif command -v shasum >/dev/null 2>&1; then
|
|
70
|
+
digest="$(printf '%s' "$principal" | shasum -a 256)"
|
|
71
|
+
else
|
|
72
|
+
die "sha256 utility is required for agent delegation groups"
|
|
73
|
+
fi
|
|
74
|
+
printf 'grp_dlg%.32s' "$digest"
|
|
75
|
+
}
|
|
76
|
+
|
|
65
77
|
GRANT_PRINCIPAL="$PRINCIPAL"
|
|
66
78
|
case "$PRINCIPAL" in
|
|
67
79
|
agt_*)
|
|
68
80
|
TAIL="$(printf '%s' "$PRINCIPAL" | tail -c 9 | tr '[:upper:]' '[:lower:]')"
|
|
69
|
-
|
|
81
|
+
LEGACY_GROUP="grp_dlg-$TAIL"
|
|
82
|
+
NEXT_GROUP="$(agent_group_id "$PRINCIPAL")"
|
|
83
|
+
RECORDED_GROUP="$(jq -r '.grantPrincipal // empty' "$MANIFEST")"
|
|
84
|
+
case "$RECORDED_GROUP" in
|
|
85
|
+
"$LEGACY_GROUP"|"$NEXT_GROUP") GRANT_PRINCIPAL="$RECORDED_GROUP" ;;
|
|
86
|
+
*) GRANT_PRINCIPAL="$NEXT_GROUP" ;;
|
|
87
|
+
esac
|
|
70
88
|
;;
|
|
71
89
|
esac
|
|
72
90
|
|
|
@@ -133,12 +151,20 @@ done
|
|
|
133
151
|
# --- 1b. agent recipients: ensure the delegation group exists + contains them
|
|
134
152
|
|
|
135
153
|
if [ "$GRANT_PRINCIPAL" != "$PRINCIPAL" ]; then
|
|
136
|
-
#
|
|
137
|
-
#
|
|
154
|
+
# Group creation and add are idempotent-tolerated; confirm that the complete
|
|
155
|
+
# member list is exactly this agent before any file ACL is shared.
|
|
138
156
|
hq groups create "$GRANT_PRINCIPAL" --name "Delegation: $DISPLAY" --company "$COMPANY" \
|
|
139
157
|
|| echo "hq-delegate-grant: group create reported an error (may already exist) — continuing" >&2
|
|
140
158
|
hq groups add "$GRANT_PRINCIPAL" "$PRINCIPAL" --company "$COMPANY" \
|
|
141
159
|
|| echo "hq-delegate-grant: group add reported an error (may already be a member) — continuing" >&2
|
|
160
|
+
GROUP_MEMBERS="$(NO_COLOR=1 FORCE_COLOR=0 hq groups members "$GRANT_PRINCIPAL" --company "$COMPANY")" \
|
|
161
|
+
|| die "could not verify delegation group membership"
|
|
162
|
+
MEMBER_IDS="$(printf '%s\n' "$GROUP_MEMBERS" | awk '
|
|
163
|
+
{ gsub(/\033\[[0-9;]*m/, "", $0) }
|
|
164
|
+
/^PERSON_UID([[:space:]]|$)/ { collecting = 1; next }
|
|
165
|
+
collecting && NF { print $1 }
|
|
166
|
+
')"
|
|
167
|
+
[ "$MEMBER_IDS" = "$PRINCIPAL" ] || die "delegation group membership does not exactly match recipient"
|
|
142
168
|
fi
|
|
143
169
|
|
|
144
170
|
# --- 2+3. exact structured read-back; never infer identity from a new row ---
|
|
@@ -93,7 +93,7 @@ if [ -f "$PRD_ABS" ] && jq -e . "$PRD_ABS" >/dev/null 2>&1; then
|
|
|
93
93
|
fi
|
|
94
94
|
|
|
95
95
|
GET_LINES="$(jq -r --arg co "$COMPANY" \
|
|
96
|
-
'.vaultPrefixes[] | "hq files get \(.prefix) --company \($co)"' "$MANIFEST")"
|
|
96
|
+
'def bashq: if test("^[A-Za-z0-9_./:@+-]+$") then . else @sh end; .vaultPrefixes[] | "hq files get \(.prefix | bashq) --company \($co | bashq)"' "$MANIFEST")"
|
|
97
97
|
|
|
98
98
|
REPO_IS_NULL="$(jq -r 'if .repo == null then "yes" else "no" end' "$MANIFEST")"
|
|
99
99
|
REPO_SECTION=""
|
|
@@ -135,7 +135,7 @@ while [ "$i" -lt "$PREFIX_COUNT" ]; do
|
|
|
135
135
|
if [ "$BROWSE_RC" -ne 0 ]; then
|
|
136
136
|
echo " FAIL $PFX — browse errored (likely cause: the grant did not land, or the caller's session expired — re-run /hq-login and hq-delegate-grant.sh)"
|
|
137
137
|
FAILED=1
|
|
138
|
-
elif [ -z "$(printf '%s' "$BROWSE_OUT" | tr -d '[:space:]')" ]; then
|
|
138
|
+
elif [ -z "$(printf '%s' "$BROWSE_OUT" | tr -d '[:space:]')" ] || [ "$(printf '%s' "$BROWSE_OUT" | tr -d '[:space:]')" = "Noobjectsunderthatprefix." ]; then
|
|
139
139
|
echo " FAIL $PFX — reachable but EMPTY (likely cause: the dossier was never pushed to the vault — hq sync push companies/$COMPANY/... and re-run)"
|
|
140
140
|
FAILED=1
|
|
141
141
|
else
|
|
@@ -156,6 +156,31 @@ done
|
|
|
156
156
|
# Browse each distinct parent at most once, and retry an empty result a few
|
|
157
157
|
# times before trusting it. (Cache dir cleaned on exit.)
|
|
158
158
|
REF_BROWSE_CACHE="$(mktemp -d)"
|
|
159
|
+
listing_has_exact_key() {
|
|
160
|
+
local listing="$1" expected="$2"
|
|
161
|
+
printf '%s\n' "$listing" | awk -v expected="$expected" '
|
|
162
|
+
function exact_key(row, expected, modified, size) {
|
|
163
|
+
gsub(/\033\[[0-9;]*m/, "", row)
|
|
164
|
+
sub(/[[:space:]]+$/, "", row)
|
|
165
|
+
if (row !~ / +(shared-with-you|creator access — creator-only|role-bypass|personal-vault|private folder — children are creator-only)$/) return 0
|
|
166
|
+
sub(/ +(shared-with-you|creator access — creator-only|role-bypass|personal-vault|private folder — children are creator-only)$/, "", row)
|
|
167
|
+
modified = row
|
|
168
|
+
sub(/^.* +/, "", modified)
|
|
169
|
+
if (modified != "—" && modified !~ /^[0-9][0-9][0-9][0-9]-[0-9][0-9]-[0-9][0-9]T[0-9][0-9]:[0-9][0-9]:[0-9][0-9]\.[0-9][0-9][0-9]Z$/) return 0
|
|
170
|
+
sub(/ +[^[:space:]]+$/, "", row)
|
|
171
|
+
sub(/[[:space:]]+$/, "", row)
|
|
172
|
+
size = row
|
|
173
|
+
sub(/^.* +/, "", size)
|
|
174
|
+
if (size !~ /^[0-9]+$/) return 0
|
|
175
|
+
sub(/ +[0-9]+$/, "", row)
|
|
176
|
+
sub(/[[:space:]]+$/, "", row)
|
|
177
|
+
return row == expected
|
|
178
|
+
}
|
|
179
|
+
NR > 2 && exact_key($0, expected) { found = 1 }
|
|
180
|
+
END { exit found ? 0 : 1 }
|
|
181
|
+
'
|
|
182
|
+
}
|
|
183
|
+
|
|
159
184
|
browse_parent() { # parent -> listing (memoized per run, retried on empty)
|
|
160
185
|
local parent="$1" key out attempt
|
|
161
186
|
key="$REF_BROWSE_CACHE/$(printf '%s' "$parent" | tr '/@:' '___')"
|
|
@@ -187,7 +212,7 @@ if [ "$REF_COUNT" -gt 0 ]; then
|
|
|
187
212
|
# early match, which under `set -o pipefail` surfaces printf's SIGPIPE as a
|
|
188
213
|
# non-zero pipeline and FALSE-FAILS a file that is genuinely present (it bit
|
|
189
214
|
# exactly the referenced files that sort early in a large parent listing).
|
|
190
|
-
if
|
|
215
|
+
if listing_has_exact_key "$LISTING" "$kpath"; then
|
|
191
216
|
echo " pass $REL"
|
|
192
217
|
else
|
|
193
218
|
echo " FAIL $REL — granted prefix is reachable but this referenced file is NOT in the vault (likely cause: it was never pushed — hq sync push $kpath --company $COMPANY and re-run)"
|
|
@@ -8,6 +8,7 @@ import { matchingSecretPatterns } from "../lib/core-utils/secret-patterns.js";
|
|
|
8
8
|
import { describeCoreCommand } from "./core-command-catalog.js";
|
|
9
9
|
import { commandExists, isRecord, jsonAt, jqFallback, jqRawPath, jqRawPaths, moveFile, readJsonFile, subprocess, writeJsonAtomically, } from "./core-delegate.js";
|
|
10
10
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
11
|
+
import { ACL_SOURCE_DISPLAY_LABELS } from "../utils/files-browse-acl.js";
|
|
11
12
|
import { runBundledScript } from "../utils/run-bundled-script.js";
|
|
12
13
|
const FLAG = "core-native-delegate";
|
|
13
14
|
const ASSETS = {
|
|
@@ -113,6 +114,42 @@ function quoteBash(value) {
|
|
|
113
114
|
return value;
|
|
114
115
|
return `'${value.replaceAll("'", `'\\''`)}'`;
|
|
115
116
|
}
|
|
117
|
+
function delegationGroupId(principal) {
|
|
118
|
+
return "grp_dlg" + createHash("sha256").update(principal).digest("hex").slice(0, 32);
|
|
119
|
+
}
|
|
120
|
+
function stripAnsiSequences(value) {
|
|
121
|
+
const escape = String.fromCharCode(27);
|
|
122
|
+
return value.replace(new RegExp(`${escape}\\[[0-?]*[ -/]*[@-~]`, "g"), "");
|
|
123
|
+
}
|
|
124
|
+
function groupMemberIds(output) {
|
|
125
|
+
const plainOutput = stripAnsiSequences(output);
|
|
126
|
+
const lines = plainOutput.split(/\r?\n/);
|
|
127
|
+
const header = lines.findIndex((line) => /^PERSON_UID(?:\s|$)/.test(line.trimStart()));
|
|
128
|
+
if (header < 0)
|
|
129
|
+
return null;
|
|
130
|
+
return lines.slice(header + 1).filter((line) => line.trim() !== "")
|
|
131
|
+
.map((line) => line.trim().split(/\s{2,}/, 1)[0] ?? "");
|
|
132
|
+
}
|
|
133
|
+
function browseContainsExactKey(listing, expected) {
|
|
134
|
+
const lines = listing.split(/\r?\n/);
|
|
135
|
+
const aclLabels = Object.values(ACL_SOURCE_DISPLAY_LABELS);
|
|
136
|
+
return lines.slice(2).some((line) => {
|
|
137
|
+
const row = stripAnsiSequences(line).trimEnd();
|
|
138
|
+
const acl = aclLabels.find((label) => row.endsWith(label));
|
|
139
|
+
if (!acl)
|
|
140
|
+
return false;
|
|
141
|
+
const beforeAcl = row.slice(0, -acl.length).trimEnd();
|
|
142
|
+
const modifiedMatch = /^(.*) +(\S+)$/.exec(beforeAcl);
|
|
143
|
+
if (!modifiedMatch)
|
|
144
|
+
return false;
|
|
145
|
+
const modified = modifiedMatch[2] ?? "";
|
|
146
|
+
if (modified !== "—" && !/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(modified))
|
|
147
|
+
return false;
|
|
148
|
+
const beforeModified = (modifiedMatch[1] ?? "").trimEnd();
|
|
149
|
+
const sizeMatch = /^(.*) +(\d+)$/.exec(beforeModified);
|
|
150
|
+
return (sizeMatch?.[1] ?? "").trimEnd() === expected;
|
|
151
|
+
});
|
|
152
|
+
}
|
|
116
153
|
function parseFlags(args, allowed) {
|
|
117
154
|
const values = {};
|
|
118
155
|
for (let i = 0; i < args.length;) {
|
|
@@ -379,8 +416,12 @@ function runGrant(args, context) {
|
|
|
379
416
|
if (!principal)
|
|
380
417
|
return fail("grant", "manifest has no to.principal");
|
|
381
418
|
let grantPrincipal = principal;
|
|
382
|
-
if (principal.startsWith("agt_"))
|
|
383
|
-
|
|
419
|
+
if (principal.startsWith("agt_")) {
|
|
420
|
+
const legacyGroup = "grp_dlg-" + principal.slice(-9).toLowerCase();
|
|
421
|
+
const nextGroup = delegationGroupId(principal);
|
|
422
|
+
const recordedGroup = text(getPath(manifest, "grantPrincipal"));
|
|
423
|
+
grantPrincipal = recordedGroup === legacyGroup || recordedGroup === nextGroup ? recordedGroup : nextGroup;
|
|
424
|
+
}
|
|
384
425
|
if (status !== "building" && status !== "granted")
|
|
385
426
|
return fail("grant", `manifest status is '${status}' — grants run from 'building' (or re-run from 'granted'), not from there`);
|
|
386
427
|
const prefixes = list(getPath(manifest, "vaultPrefixes"));
|
|
@@ -431,6 +472,13 @@ function runGrant(args, context) {
|
|
|
431
472
|
result = runHq(["groups", "add", grantPrincipal, principal, "--company", company], context, "inherit");
|
|
432
473
|
if (result.code !== 0)
|
|
433
474
|
err("hq-delegate-grant: group add reported an error (may already be a member) — continuing\n");
|
|
475
|
+
const members = subprocess("hq", ["groups", "members", grantPrincipal, "--company", company], context.cwd, { env: { NO_COLOR: "1", FORCE_COLOR: "0" } });
|
|
476
|
+
if (members.code !== 0)
|
|
477
|
+
return fail("grant", "could not verify delegation group membership");
|
|
478
|
+
const memberIds = groupMemberIds(members.stdout ?? "");
|
|
479
|
+
if (memberIds === null || memberIds.length !== 1 || memberIds[0] !== principal) {
|
|
480
|
+
return fail("grant", "delegation group membership does not exactly match recipient");
|
|
481
|
+
}
|
|
434
482
|
}
|
|
435
483
|
let expectedType = "";
|
|
436
484
|
let expectedId = "";
|
|
@@ -794,7 +842,7 @@ function runSend(args, context) {
|
|
|
794
842
|
.map((entry, index) => `${index + 1}. **${text(getPath(entry, "id"))}: ${text(getPath(entry, "title"))}** — ${text(getPath(entry, "description"))}`)
|
|
795
843
|
.join("\n");
|
|
796
844
|
}
|
|
797
|
-
const getLines = list(getPath(manifest, "vaultPrefixes", [])).map((entry) =>
|
|
845
|
+
const getLines = list(getPath(manifest, "vaultPrefixes", [])).map((entry) => "hq files get " + quoteBash(jqRawPath(entry, "prefix")) + " --company " + quoteBash(company)).join("\n");
|
|
798
846
|
let repoSection = "";
|
|
799
847
|
const repoValue = getPath(manifest, "repo");
|
|
800
848
|
if (repoValue !== null && repoValue !== undefined) {
|
|
@@ -824,7 +872,7 @@ function runSend(args, context) {
|
|
|
824
872
|
body += `## The goal\n\n${goal}\n\n`;
|
|
825
873
|
if (stateLine)
|
|
826
874
|
body += `## Where things stand\n\n${stateLine}\n\n`;
|
|
827
|
-
body += `## Pull the files (on demand — no sync)\n\nRun these from your HQ root; each materializes exactly that path:\n\n\`\`\`bash\n${getLines}\n\`\`\`\n\nStart with the project dossier: \`companies/${company}/projects/${project}/\` now contains the PRD (\`prd.json\`), a full brief (\`README.md\` / the DM's details pane), and the delegation journal.\n\n`;
|
|
875
|
+
body += `## Pull the files (on demand — no sync)\n\nRun these from your HQ root; each materializes exactly that path locally:\n\n\`\`\`bash\n${getLines}\n\`\`\`\n\nStart with the project dossier: \`companies/${company}/projects/${project}/\` now contains the PRD (\`prd.json\`), a full brief (\`README.md\` / the DM's details pane), and the delegation journal.\n\n`;
|
|
828
876
|
if (repoSection)
|
|
829
877
|
body += `${repoSection}\n\n`;
|
|
830
878
|
if (secretsSection)
|
|
@@ -1024,7 +1072,7 @@ function runVerify(args, context) {
|
|
|
1024
1072
|
out(` FAIL ${prefix} — browse errored (likely cause: the grant did not land, or the caller's session expired — re-run /hq-login and hq-delegate-grant.sh)\n`);
|
|
1025
1073
|
failed = true;
|
|
1026
1074
|
}
|
|
1027
|
-
else if (!combined.replace(/\s/g, "")) {
|
|
1075
|
+
else if (!combined.replace(/\s/g, "") || combined.replace(/\s/g, "") === "Noobjectsunderthatprefix.") {
|
|
1028
1076
|
out(` FAIL ${prefix} — reachable but EMPTY (likely cause: the dossier was never pushed to the vault — hq sync push companies/${company}/... and re-run)\n`);
|
|
1029
1077
|
failed = true;
|
|
1030
1078
|
}
|
|
@@ -1059,9 +1107,8 @@ function runVerify(args, context) {
|
|
|
1059
1107
|
const relative = reference.slice(`companies/${company}/`.length);
|
|
1060
1108
|
const separator = relative.lastIndexOf("/");
|
|
1061
1109
|
const parent = `${separator < 0 ? "." : relative.slice(0, separator)}/`;
|
|
1062
|
-
const base = separator < 0 ? relative : relative.slice(separator + 1);
|
|
1063
1110
|
const listing = browseParent(parent);
|
|
1064
|
-
if (listing
|
|
1111
|
+
if (browseContainsExactKey(listing, reference))
|
|
1065
1112
|
out(` pass ${relative}\n`);
|
|
1066
1113
|
else {
|
|
1067
1114
|
out(` FAIL ${relative} — granted prefix is reachable but this referenced file is NOT in the vault (likely cause: it was never pushed — hq sync push ${reference} --company ${company} and re-run)\n`);
|
|
@@ -23,6 +23,7 @@ export declare function subprocess(command: string, args: string[], cwd: string,
|
|
|
23
23
|
stdinFd?: number;
|
|
24
24
|
stderr?: "inherit" | "ignore" | "pipe";
|
|
25
25
|
stdout?: "capture" | "inherit" | "ignore";
|
|
26
|
+
env?: NodeJS.ProcessEnv;
|
|
26
27
|
maxBuffer?: number;
|
|
27
28
|
}): CommandResult;
|
|
28
29
|
type JsonRecord = Record<string, unknown>;
|
|
@@ -89,7 +89,7 @@ export function commandExists(name, cwd = process.cwd(), options = {}) {
|
|
|
89
89
|
export function subprocess(command, args, cwd, options = {}) {
|
|
90
90
|
const result = spawnSync(command, args, {
|
|
91
91
|
cwd,
|
|
92
|
-
env: process.env,
|
|
92
|
+
env: { ...process.env, ...options.env },
|
|
93
93
|
input: options.input,
|
|
94
94
|
maxBuffer: options.maxBuffer,
|
|
95
95
|
encoding: "buffer",
|
|
@@ -36,6 +36,9 @@
|
|
|
36
36
|
import { Command } from "commander";
|
|
37
37
|
import { ListObjectsV2Command, GetObjectCommand, type ListObjectsV2CommandOutput, type GetObjectCommandOutput } from "@aws-sdk/client-s3";
|
|
38
38
|
import { VaultClient, type ExplicitGrant } from "@indigoai-us/hq-cloud";
|
|
39
|
+
import { type AclSource } from "../utils/files-browse-acl.js";
|
|
40
|
+
export { ACL_SOURCE_DISPLAY_LABELS, displayAclSource } from "../utils/files-browse-acl.js";
|
|
41
|
+
export type { AclSource } from "../utils/files-browse-acl.js";
|
|
39
42
|
/** STS-vended credential set the browse/cat path consumes. */
|
|
40
43
|
export interface BrowseCredentials {
|
|
41
44
|
accessKeyId: string;
|
|
@@ -121,7 +124,6 @@ export type CompanyBrowseClientFactory = (input: {
|
|
|
121
124
|
* - `private-folder`: this is the private folder row itself. Its children
|
|
122
125
|
* are creator-only, so it never covers a child key.
|
|
123
126
|
*/
|
|
124
|
-
export type AclSource = "shared-with-you" | "creator" | "role-bypass" | "personal-vault" | "private-folder";
|
|
125
127
|
export interface BrowseRow {
|
|
126
128
|
key: string;
|
|
127
129
|
size: number;
|
|
@@ -505,5 +507,4 @@ export declare function resolveBrowseSession(): Promise<{
|
|
|
505
507
|
export declare function registerFilesBrowseCommands(filesCmd: Command, deps?: {
|
|
506
508
|
personalS3Factory?: S3ClientFactory;
|
|
507
509
|
}): void;
|
|
508
|
-
export {};
|
|
509
510
|
//# sourceMappingURL=files-browse.d.ts.map
|
|
@@ -42,6 +42,8 @@ import { pipeline } from "node:stream/promises";
|
|
|
42
42
|
import { S3Client, ListObjectsV2Command, GetObjectCommand, } from "@aws-sdk/client-s3";
|
|
43
43
|
import { VaultClient, } from "@indigoai-us/hq-cloud";
|
|
44
44
|
import { isPrivateFolderPattern } from "./_patterns.js";
|
|
45
|
+
import { displayAclSource, } from "../utils/files-browse-acl.js";
|
|
46
|
+
export { ACL_SOURCE_DISPLAY_LABELS, displayAclSource } from "../utils/files-browse-acl.js";
|
|
45
47
|
import { DEFAULT_HQ_ROOT, DEFAULT_COGNITO, buildVaultConfig, } from "../utils/cognito-session.js";
|
|
46
48
|
import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
|
|
47
49
|
import { maxLength } from "../utils/table-width.js";
|
|
@@ -142,13 +144,6 @@ export function toBrowseGrantPattern(grantPath, slug) {
|
|
|
142
144
|
const companyRelative = toBucketRelative(grantPath, slug);
|
|
143
145
|
return companyRelative === "*" ? "" : companyRelative;
|
|
144
146
|
}
|
|
145
|
-
function displayAclSource(source) {
|
|
146
|
-
return source === "private-folder"
|
|
147
|
-
? "private folder — children are creator-only"
|
|
148
|
-
: source === "creator"
|
|
149
|
-
? "creator access — creator-only"
|
|
150
|
-
: source;
|
|
151
|
-
}
|
|
152
147
|
/**
|
|
153
148
|
* Bright-line guard for `--out`: refuse to write any byte beneath
|
|
154
149
|
* `<hqRoot>/companies/`. We do NOT enumerate `companies/manifest.yaml`
|
|
@@ -448,6 +448,11 @@ export function createLaneMessageHandlers(deps) {
|
|
|
448
448
|
text: promptedText,
|
|
449
449
|
});
|
|
450
450
|
const deliveryText = framedMonitorEvent ?? promptedText;
|
|
451
|
+
const messageHistoryTrigger = opts.monitorEvent !== undefined
|
|
452
|
+
? "monitor_event"
|
|
453
|
+
: opts.messageSource ?? "message";
|
|
454
|
+
const isMonitorEvent = messageHistoryTrigger === "monitor_event";
|
|
455
|
+
const isSystemNotice = messageHistoryTrigger === "system_notice";
|
|
451
456
|
const dropboxMessage = {
|
|
452
457
|
at,
|
|
453
458
|
message_id: messageId,
|
|
@@ -562,9 +567,9 @@ export function createLaneMessageHandlers(deps) {
|
|
|
562
567
|
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
563
568
|
...(hireGrantContext !== undefined ? { grantContext: hireGrantContext } : {}),
|
|
564
569
|
pending: {
|
|
565
|
-
trigger:
|
|
570
|
+
trigger: messageHistoryTrigger,
|
|
566
571
|
text: deliveryText,
|
|
567
|
-
...(
|
|
572
|
+
...(isMonitorEvent ? { monitor_event: true } : {}),
|
|
568
573
|
message_id: messageId,
|
|
569
574
|
compact: opts.compact !== false,
|
|
570
575
|
},
|
|
@@ -589,9 +594,9 @@ export function createLaneMessageHandlers(deps) {
|
|
|
589
594
|
...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
|
|
590
595
|
...(hireGrantContext !== undefined ? { grantContext: hireGrantContext } : {}),
|
|
591
596
|
pending: {
|
|
592
|
-
trigger:
|
|
597
|
+
trigger: messageHistoryTrigger,
|
|
593
598
|
text: deliveryText,
|
|
594
|
-
...(
|
|
599
|
+
...(isMonitorEvent ? { monitor_event: true } : {}),
|
|
595
600
|
message_id: messageId,
|
|
596
601
|
compact: opts.compact !== false,
|
|
597
602
|
},
|
|
@@ -674,10 +679,10 @@ export function createLaneMessageHandlers(deps) {
|
|
|
674
679
|
queued_at: at,
|
|
675
680
|
pending_resume: {
|
|
676
681
|
session_id: sessionId,
|
|
677
|
-
trigger:
|
|
682
|
+
trigger: messageHistoryTrigger,
|
|
678
683
|
caller: ctx.env.HQ_LANE_ID?.trim() || "user",
|
|
679
684
|
text: deliveryText,
|
|
680
|
-
...(
|
|
685
|
+
...(isMonitorEvent ? { monitor_event: true } : {}),
|
|
681
686
|
message_id: messageId,
|
|
682
687
|
compact: opts.compact !== false,
|
|
683
688
|
at,
|
|
@@ -708,7 +713,8 @@ export function createLaneMessageHandlers(deps) {
|
|
|
708
713
|
kind: "message",
|
|
709
714
|
text: deliveryText,
|
|
710
715
|
message_id: messageId,
|
|
711
|
-
...(
|
|
716
|
+
...(isMonitorEvent ? { monitor_event: true } : {}),
|
|
717
|
+
...(isSystemNotice ? { system_notice: true } : {}),
|
|
712
718
|
},
|
|
713
719
|
compact: opts.compact !== false,
|
|
714
720
|
effort: resumeEffort ?? recordedResumeEffort(lane),
|
|
@@ -868,6 +874,11 @@ export function createLaneMessageHandlers(deps) {
|
|
|
868
874
|
try {
|
|
869
875
|
await runMessage(ctx, laneId, {
|
|
870
876
|
text: formatDropboxMessages(messages),
|
|
877
|
+
messageSource: messages.some((message) => message.kind === "monitor_event")
|
|
878
|
+
? "monitor_event"
|
|
879
|
+
: messages.some((message) => message.kind === "system_notice")
|
|
880
|
+
? "system_notice"
|
|
881
|
+
: undefined,
|
|
871
882
|
compact: false,
|
|
872
883
|
silent: true,
|
|
873
884
|
onAccepted: () => { accepted = true; },
|
|
@@ -52,6 +52,8 @@ export interface LaneMessageOptions {
|
|
|
52
52
|
description: string;
|
|
53
53
|
sequence: number;
|
|
54
54
|
};
|
|
55
|
+
/** Source kind for a message recovered from the lane dropbox. */
|
|
56
|
+
messageSource?: "monitor_event" | "system_notice";
|
|
55
57
|
/** Internal recovery messages do not emit a second command response. */
|
|
56
58
|
silent?: boolean;
|
|
57
59
|
/** Internal recovery claim is safe to archive once this callback runs. */
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { accountStatus, coveredLaneIds, loadLanesConfig, MAX_WATCHED_LANES, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, monitorReminderText, runLaneWatch, } from "../lib/lanes/index.js";
|
|
2
|
-
import { LEAD_LANE_KEEP_ALIVE_FLAG
|
|
2
|
+
import { LEAD_LANE_KEEP_ALIVE_FLAG } from "../lib/lanes/keep-alive.js";
|
|
3
3
|
import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
|
|
4
4
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
5
5
|
import { isLanesError, LanesError } from "../lib/lanes/errors.js";
|
|
@@ -50,10 +50,6 @@ export function createLaneWatchHandlers(deps) {
|
|
|
50
50
|
company: resolveLaneCompany(hqRoot, lane).company ?? undefined,
|
|
51
51
|
env: ctx.env,
|
|
52
52
|
}),
|
|
53
|
-
keepAliveNextCheckAtEnabled: (lane) => readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, {
|
|
54
|
-
company: resolveLaneCompany(hqRoot, lane).company ?? undefined,
|
|
55
|
-
env: ctx.env,
|
|
56
|
-
}),
|
|
57
53
|
readAccountStatuses: async (lanes) => {
|
|
58
54
|
if (lanes.length === 0)
|
|
59
55
|
return [];
|
package/dist/commands/lanes.js
CHANGED
|
@@ -61,7 +61,7 @@ import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STA
|
|
|
61
61
|
import { laneStories, storyWarning, } from "../lib/lanes/stories.js";
|
|
62
62
|
import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
|
|
63
63
|
import { resolveLaneCompany } from "../lib/lanes/lane-company.js";
|
|
64
|
-
import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG,
|
|
64
|
+
import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
65
65
|
import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
|
|
66
66
|
import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
|
|
67
67
|
import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
|
|
@@ -2962,6 +2962,7 @@ async function runCreate(ctx, opts) {
|
|
|
2962
2962
|
const config = loadLanesConfig(hqRoot);
|
|
2963
2963
|
let outpostId = opts.outpost?.trim() || undefined;
|
|
2964
2964
|
let placementHostOverride;
|
|
2965
|
+
let placementBalanceHostOverride;
|
|
2965
2966
|
let autoPlacementChoice;
|
|
2966
2967
|
let autoPlacementSkippedHosts = [];
|
|
2967
2968
|
let placementMachineIdentity;
|
|
@@ -3013,12 +3014,14 @@ async function runCreate(ctx, opts) {
|
|
|
3013
3014
|
if (placementToApply !== undefined) {
|
|
3014
3015
|
const currentHost = laneHostFromCtx(ctx);
|
|
3015
3016
|
if (placementToApply.kind === "local") {
|
|
3016
|
-
|
|
3017
|
+
placementBalanceHostOverride = currentHost;
|
|
3018
|
+
placementHostOverride = { kind: "local", id: ctx.hostname() };
|
|
3017
3019
|
outpostId = undefined;
|
|
3018
3020
|
}
|
|
3019
3021
|
else if (placementToApply.kind === "outpost") {
|
|
3020
3022
|
if (currentHost.kind === "outpost" && currentHost.id === placementToApply.id) {
|
|
3021
|
-
|
|
3023
|
+
placementBalanceHostOverride = currentHost;
|
|
3024
|
+
placementHostOverride = { kind: "local", id: ctx.hostname() };
|
|
3022
3025
|
outpostId = undefined;
|
|
3023
3026
|
}
|
|
3024
3027
|
else {
|
|
@@ -3275,7 +3278,7 @@ async function runCreate(ctx, opts) {
|
|
|
3275
3278
|
const allowedAccounts = validatedAccounts.size > 0 ? [...validatedAccounts] : profileAccounts;
|
|
3276
3279
|
let accountSelection;
|
|
3277
3280
|
if (provider === "claude") {
|
|
3278
|
-
const targetHost = placementHostOverride ?? (outpostId !== undefined
|
|
3281
|
+
const targetHost = placementBalanceHostOverride ?? placementHostOverride ?? (outpostId !== undefined
|
|
3279
3282
|
? { kind: "outpost", id: outpostId }
|
|
3280
3283
|
: laneHostFromCtx(ctx));
|
|
3281
3284
|
balanceTargetHost = targetHost;
|
|
@@ -5159,17 +5162,11 @@ async function runShow(ctx, laneId, hasPendingQuestionForLane) {
|
|
|
5159
5162
|
const keepAliveState = resolveLaneKeepAlive(hqRoot, storedLane);
|
|
5160
5163
|
const keepAliveCompany = resolveLaneCompany(hqRoot, storedLane);
|
|
5161
5164
|
const now = ctx.now().getTime();
|
|
5162
|
-
const
|
|
5163
|
-
? await readLanesRegistryFlag(LEAD_LANE_KEEP_ALIVE_NEXT_CHECK_FLAG, {
|
|
5164
|
-
company: keepAliveCompany.company ?? undefined,
|
|
5165
|
-
env: ctx.env,
|
|
5166
|
-
})
|
|
5167
|
-
: false;
|
|
5168
|
-
const hasPendingWork = storedLane.keep_alive_standing === true || nextCheckAtEnabled
|
|
5165
|
+
const hasPendingWork = storedLane.state === "done" && storedLane.keep_alive === true
|
|
5169
5166
|
? hasPendingKeepAliveWork(hqRoot, storedLane)
|
|
5170
5167
|
: false;
|
|
5171
5168
|
const doneStateVerdict = storedLane.state === "done" && storedLane.keep_alive === true
|
|
5172
|
-
? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete, next_check_at: storedLane.last_envelope?.next_check_at }, storedLane.keep_alive_standing === true && hasPendingWork, {
|
|
5169
|
+
? doneKeepAliveVerdict(storedLane, { decision: storedLane.last_envelope?.decision, story_complete: storedLane.story_complete, next_check_at: storedLane.last_envelope?.next_check_at }, storedLane.keep_alive_standing === true && hasPendingWork, { hasPendingWork, nowMs: now })
|
|
5173
5170
|
: null;
|
|
5174
5171
|
let managerStatusTarget;
|
|
5175
5172
|
let managerStatusGateError;
|
|
@@ -5191,7 +5188,7 @@ async function runShow(ctx, laneId, hasPendingQuestionForLane) {
|
|
|
5191
5188
|
now: ctx.now().getTime(),
|
|
5192
5189
|
});
|
|
5193
5190
|
const laneIsSenior = allLanes.some((item) => item.senior.kind === "lane" && item.senior.id === laneId);
|
|
5194
|
-
const requestedNextCheckMs =
|
|
5191
|
+
const requestedNextCheckMs = storedLane.state === "done" && storedLane.keep_alive === true && !hasPendingWork
|
|
5195
5192
|
? keepAliveNextCheckAtMs(storedLane.last_envelope?.next_check_at, now, Date.parse(storedLane.last_envelope?.at ?? storedLane.updated_at))
|
|
5196
5193
|
: undefined;
|
|
5197
5194
|
const storedNextEligibleMs = Date.parse(storedLane.keep_alive_next_eligible_at ?? "");
|
package/dist/fast-scaffold.d.ts
CHANGED
|
@@ -16,6 +16,8 @@ export type FastScaffoldScript = {
|
|
|
16
16
|
/** Path under `assets/scaffold/`, e.g. `core/scripts/foo.sh`. */
|
|
17
17
|
asset: string;
|
|
18
18
|
root?: "live";
|
|
19
|
+
/** Whether the bundled fast-route module closure needs Node's network timeout patch. */
|
|
20
|
+
requiresNodeNetworkCompatibility?: boolean;
|
|
19
21
|
} | {
|
|
20
22
|
/** Command path, joined with spaces, e.g. `policy frontmatter`. */
|
|
21
23
|
name: string;
|
|
@@ -38,6 +40,8 @@ export type FastScaffoldScript = {
|
|
|
38
40
|
shellHqRoot?: "none" | "live" | "reconcile-default";
|
|
39
41
|
/** Whether this handler consumes stdin. Shell companions inherit stdin directly. */
|
|
40
42
|
readsStdin?: boolean;
|
|
43
|
+
/** Whether the bundled fast-route module closure needs Node's network timeout patch. */
|
|
44
|
+
requiresNodeNetworkCompatibility?: boolean;
|
|
41
45
|
};
|
|
42
46
|
/** The relocated scripts that qualify for the entrypoint fast path. */
|
|
43
47
|
export declare const FAST_SCAFFOLD_SCRIPTS: readonly FastScaffoldScript[];
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -115,6 +115,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
115
115
|
flagKey: "hq-core-native-fast-route",
|
|
116
116
|
familyFlagKey: "core-native-handoff",
|
|
117
117
|
asset: "core/scripts/worktree-gc.sh",
|
|
118
|
+
requiresNodeNetworkCompatibility: true,
|
|
118
119
|
root: "live",
|
|
119
120
|
shellCwd: "live",
|
|
120
121
|
shellHqRoot: "live",
|
|
@@ -127,6 +128,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
127
128
|
flagKey: "hq-core-native-fast-route",
|
|
128
129
|
familyFlagKey: "core-native-handoff",
|
|
129
130
|
asset: "core/scripts/clean-worktree-reconcile-handoff.sh",
|
|
131
|
+
requiresNodeNetworkCompatibility: true,
|
|
130
132
|
root: "cwd",
|
|
131
133
|
shellCwd: "caller",
|
|
132
134
|
shellHqRoot: "none",
|
|
@@ -139,6 +141,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
139
141
|
flagKey: "hq-core-native-fast-route",
|
|
140
142
|
familyFlagKey: "core-native-handoff",
|
|
141
143
|
asset: "core/scripts/resume-thread-lock.sh",
|
|
144
|
+
requiresNodeNetworkCompatibility: true,
|
|
142
145
|
root: "live",
|
|
143
146
|
shellCwd: "live",
|
|
144
147
|
shellHqRoot: "live",
|
|
@@ -151,6 +154,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
151
154
|
flagKey: "hq-core-native-fast-route",
|
|
152
155
|
familyFlagKey: "core-native-handoff",
|
|
153
156
|
asset: "core/scripts/verify-story-deliverables.sh",
|
|
157
|
+
requiresNodeNetworkCompatibility: true,
|
|
154
158
|
root: "live",
|
|
155
159
|
shellCwd: "live",
|
|
156
160
|
shellHqRoot: "live",
|
|
@@ -163,6 +167,7 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
163
167
|
flagKey: "hq-core-native-fast-route",
|
|
164
168
|
familyFlagKey: "core-native-jobs",
|
|
165
169
|
asset: `core/scripts/${name === "reconcile" ? "outpost-jobs-reconcile" : `jobs-${name}`}.sh`,
|
|
170
|
+
requiresNodeNetworkCompatibility: true,
|
|
166
171
|
root: name === "reconcile" ? "passthrough" : "cwd",
|
|
167
172
|
shellCwd: "caller",
|
|
168
173
|
shellHqRoot: name === "reconcile" ? "reconcile-default" : "none",
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import "./node-preflight.js";
|
|
3
|
-
import "./node-network-compat.js";
|
|
3
|
+
import "./node-network-compat-fast.js";
|
|
4
4
|
import { isVersionRequest } from "./version-request.js";
|
|
5
5
|
import { isFastCoreRequest } from "./fast-scaffold.js";
|
|
6
6
|
export declare const __test__: {
|
package/dist/index.js
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
// MUST be first: guard the Node version before any dependency that needs a
|
|
3
3
|
// Node 20+ API (e.g. util.styleText) or a newer native ABI is evaluated.
|
|
4
4
|
import "./node-preflight.js";
|
|
5
|
-
import "./node-network-compat.js";
|
|
5
|
+
import "./node-network-compat-fast.js";
|
|
6
6
|
import { isVersionRequest, wantsLabelledVersionReport } from "./version-request.js";
|
|
7
7
|
// Dependency-light: a pure parser + table, no command graph. Safe to load on
|
|
8
8
|
// every path (including --version) without reintroducing the heavy startup.
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type WorkerOptions } from "node:worker_threads";
|
|
9
9
|
import { createFlagClient, type FlagClient, type FlagLookupOptions, type FlagSnapshot } from "@indigoai-us/hq-flags-client";
|
|
10
|
+
type FlagRegistrySentry = Pick<typeof import("@sentry/node"), "captureException" | "withScope">;
|
|
10
11
|
export { resolveProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
11
12
|
export type { ProcessFlagCompanyContext } from "./process-flag-company-context.js";
|
|
12
13
|
export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
|
|
@@ -20,6 +21,7 @@ interface WorkerLike {
|
|
|
20
21
|
export interface FlagRegistryDependencies {
|
|
21
22
|
endpoint?: string;
|
|
22
23
|
createWorker?: (filename: string | URL, options: WorkerOptions) => WorkerLike;
|
|
24
|
+
sentry?: FlagRegistrySentry;
|
|
23
25
|
/** Test seam for exercising the no-source fail-open path. */
|
|
24
26
|
workerSource?: string;
|
|
25
27
|
captureWorkerFailure?: (error: Error, tags: Record<string, string>) => void;
|