@indigoai-us/hq-cli 5.300.0 → 5.302.0
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 +42 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +99 -12
- package/assets/scaffold/core/scripts/token-usage-report.sh +9 -0
- package/dist/command-catalog.generated.d.ts +32 -3
- package/dist/command-catalog.generated.js +39 -3
- package/dist/commands/__fixtures__/access-vault.d.ts +2 -0
- package/dist/commands/__fixtures__/access-vault.js +1 -1
- package/dist/commands/access.d.ts +2 -1
- package/dist/commands/access.js +58 -49
- package/dist/commands/bot.js +1 -0
- package/dist/commands/cloud-provision.js +51 -2
- package/dist/commands/core-checkpoint.js +1 -0
- package/dist/commands/core-company-index-scope-flag.d.ts +3 -0
- package/dist/commands/core-company-index-scope-flag.js +6 -0
- package/dist/commands/core-handoff-fast.js +1 -0
- package/dist/commands/core-handoff.js +2 -0
- package/dist/commands/core-job-native-utils.d.ts +6 -0
- package/dist/commands/core-job-native-utils.js +80 -0
- package/dist/commands/core-job-native.d.ts +2 -4
- package/dist/commands/core-job-native.js +6 -10
- package/dist/commands/core-job-notify-native.d.ts +36 -0
- package/dist/commands/core-job-notify-native.js +359 -0
- package/dist/commands/core-job-remediate-native.d.ts +26 -0
- package/dist/commands/core-job-remediate-native.js +374 -0
- package/dist/commands/core.js +26 -8
- package/dist/commands/deploy-comments.js +5 -2
- package/dist/commands/files.js +31 -14
- package/dist/commands/integrations-api.d.ts +6 -6
- package/dist/commands/integrations-api.js +4 -13
- package/dist/commands/integrations-connect.d.ts +3 -0
- package/dist/commands/integrations-connect.js +21 -2
- package/dist/commands/integrations-manage.js +1 -4
- package/dist/commands/lanes-message.js +43 -5
- package/dist/commands/lanes-questions.d.ts +4 -2
- package/dist/commands/lanes-questions.js +123 -2
- package/dist/commands/lanes-resume.js +25 -4
- package/dist/commands/lanes-shared.d.ts +15 -1
- package/dist/commands/lanes-watch.js +5 -1
- package/dist/commands/lanes.d.ts +2 -0
- package/dist/commands/lanes.js +92 -9
- package/dist/commands/meetings.js +35 -16
- package/dist/commands/monitor.js +17 -2
- package/dist/commands/report.js +5 -2
- package/dist/commands/rescue.d.ts +4 -0
- package/dist/commands/rescue.js +27 -17
- package/dist/commands/safe-extract.d.ts +4 -4
- package/dist/commands/safe-extract.js +6 -7
- package/dist/lib/agent-kit/fallback.d.ts +1 -0
- package/dist/lib/agent-kit/fallback.js +1 -0
- package/dist/lib/bot/daemon.d.ts +5 -1
- package/dist/lib/bot/daemon.js +4 -4
- package/dist/lib/core-utils/soft-timeout.js +1 -1
- package/dist/lib/core-utils/token-usage-report.js +17 -3
- package/dist/lib/deploy-apps.d.ts +13 -0
- package/dist/lib/deploy-apps.js +24 -0
- package/dist/lib/index-render/companies.d.ts +4 -1
- package/dist/lib/index-render/companies.js +66 -3
- package/dist/lib/index-render/index.d.ts +1 -0
- package/dist/lib/index-render/index.js +2 -1
- package/dist/lib/index-render/projects.js +82 -5
- package/dist/lib/index-render/shared.d.ts +1 -1
- package/dist/lib/index-render/shared.js +4 -2
- package/dist/lib/lanes/delivery.d.ts +7 -3
- package/dist/lib/lanes/delivery.js +18 -8
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +13 -3
- package/dist/lib/lanes/keep-alive.js +29 -5
- package/dist/lib/lanes/outpost-dispatch.d.ts +5 -0
- package/dist/lib/lanes/outpost-dispatch.js +12 -0
- package/dist/lib/lanes/reconcile.d.ts +1 -0
- package/dist/lib/lanes/reconcile.js +358 -38
- package/dist/lib/lanes/spawn.d.ts +1 -0
- package/dist/lib/lanes/spawn.js +20 -10
- package/dist/lib/lanes/store.js +24 -0
- package/dist/lib/lanes/story-ledger.d.ts +2 -0
- package/dist/lib/lanes/story-ledger.js +8 -0
- package/dist/lib/lanes/types.d.ts +26 -4
- package/dist/lib/lanes/types.js +33 -1
- package/dist/lib/lanes/watch.d.ts +1 -0
- package/dist/lib/lanes/watch.js +19 -6
- package/dist/lib/lanes/watcher.js +139 -9
- package/dist/lib/mesh/live/daemon/mqtt-events.d.ts +7 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.js +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/mqtt-events.test-d.js +10 -0
- package/dist/lib/mesh/live/daemon/presence.d.ts +2 -2
- package/dist/lib/mesh/live/daemon/run.js +36 -2
- package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +2 -2
- package/dist/lib/search-index/background.d.ts +1 -0
- package/dist/lib/search-index/background.js +1 -0
- package/dist/utils/self-update.js +51 -4
- package/dist/utils/staged-npm-install.d.ts +16 -0
- package/dist/utils/staged-npm-install.js +264 -0
- package/dist/utils/update-command-supervisor.cjs +54 -1
- package/dist/utils/version-gate.d.ts +8 -0
- package/dist/utils/version-gate.js +83 -16
- package/package.json +2 -2
- package/dist/lib/install-path-compatibility-flag.d.ts +0 -9
- package/dist/lib/install-path-compatibility-flag.js +0 -12
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
|
+
import * as path from "node:path";
|
|
3
|
+
import * as os from "node:os";
|
|
4
|
+
import { spawnSync } from "node:child_process";
|
|
5
|
+
import { serializeJqJson } from "../lib/core-utils/jq-json-serializer.js";
|
|
6
|
+
import { registerCoreJobLeaf } from "./core-job-native-utils.js";
|
|
7
|
+
const ASSET = "core/scripts/hq-job-remediate.sh";
|
|
8
|
+
const USAGE = [
|
|
9
|
+
"hq-job-remediate.sh — bounded self-remediation for Outpost scheduled jobs (US-006).",
|
|
10
|
+
"",
|
|
11
|
+
"Allowlist (v1):",
|
|
12
|
+
" - Cognito / HQ token refresh via `hq auth refresh` when available",
|
|
13
|
+
" - one `hq sync pull` (personal or --company)",
|
|
14
|
+
" - single immediate retry for transient infra (signaled via retry_recommended)",
|
|
15
|
+
"Always-human (never auto-looped):",
|
|
16
|
+
" - device re-login (Claude/Codex CLI)",
|
|
17
|
+
" - secret share / missing vault grants",
|
|
18
|
+
"",
|
|
19
|
+
"Usage:",
|
|
20
|
+
" core/scripts/hq-job-remediate.sh --failure-class <class> [--job-id <id>] \\",
|
|
21
|
+
" [--company <slug>] [--hq-root <dir>]",
|
|
22
|
+
"",
|
|
23
|
+
"Env / overrides (tests):",
|
|
24
|
+
" HQ_ROOT, HQ_JOB_REMEDIATE_AUTH_REFRESH_CMD, HQ_JOB_REMEDIATE_SYNC_PULL_CMD",
|
|
25
|
+
" HQ_JOB_REMEDIATE_DRY=1 — print plan only; do not execute side effects",
|
|
26
|
+
"",
|
|
27
|
+
"stdout: JSON {failure_class, attempted, actions[], retry_recommended, always_human, next_action, notes}",
|
|
28
|
+
"Exit: 0 always on successful classify/plan (even if nothing attempted); 2 usage.",
|
|
29
|
+
"",
|
|
30
|
+
"set -euo pipefail",
|
|
31
|
+
"",
|
|
32
|
+
].join("\n");
|
|
33
|
+
function defaultCommandExists(command, env) {
|
|
34
|
+
if (command.includes(path.sep)) {
|
|
35
|
+
try {
|
|
36
|
+
fs.accessSync(command, fs.constants.X_OK);
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
for (const directory of (env.PATH ?? "").split(path.delimiter)) {
|
|
44
|
+
if (!directory)
|
|
45
|
+
continue;
|
|
46
|
+
try {
|
|
47
|
+
fs.accessSync(path.join(directory, command), fs.constants.X_OK);
|
|
48
|
+
return true;
|
|
49
|
+
}
|
|
50
|
+
catch { /* Continue PATH lookup. */ }
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
function defaultRun(command, args, options) {
|
|
55
|
+
// The shell redirects both streams to the append-only log (or /dev/null). No
|
|
56
|
+
// timeout is set, matching its direct command invocation exactly.
|
|
57
|
+
let fd;
|
|
58
|
+
try {
|
|
59
|
+
fd = fs.openSync(options.logPath || os.devNull, "a");
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
// Bash reports failed redirection as a nonzero action; remediation still
|
|
63
|
+
// emits its classification JSON and exit status 0.
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
try {
|
|
67
|
+
const result = spawnSync(command, args, {
|
|
68
|
+
cwd: options.cwd,
|
|
69
|
+
env: options.env,
|
|
70
|
+
stdio: ["ignore", fd, fd],
|
|
71
|
+
});
|
|
72
|
+
if (result.error) {
|
|
73
|
+
fs.writeSync(fd, `${result.error.message}\n`);
|
|
74
|
+
return 127;
|
|
75
|
+
}
|
|
76
|
+
return result.status ?? 0;
|
|
77
|
+
}
|
|
78
|
+
finally {
|
|
79
|
+
fs.closeSync(fd);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
export function defaultJobRemediateDependencies(env = process.env, cwd = process.cwd()) {
|
|
83
|
+
return {
|
|
84
|
+
env,
|
|
85
|
+
cwd,
|
|
86
|
+
readFile: (file) => fs.readFileSync(file, "utf8"),
|
|
87
|
+
commandExists: (command) => defaultCommandExists(command, env),
|
|
88
|
+
run: defaultRun,
|
|
89
|
+
stdout: (text) => { process.stdout.write(text); },
|
|
90
|
+
stderr: (text) => { process.stderr.write(text); },
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
function parseArguments(argv, deps) {
|
|
94
|
+
const values = { hqRoot: deps.env.HQ_ROOT ?? "", failureClass: "", jobId: "", company: "", dryRun: false };
|
|
95
|
+
const aliases = {
|
|
96
|
+
"--hq-root": "hqRoot", "--failure-class": "failureClass", "--job-id": "jobId", "--company": "company",
|
|
97
|
+
};
|
|
98
|
+
for (let index = 0; index < argv.length;) {
|
|
99
|
+
const flag = argv[index];
|
|
100
|
+
if (flag === "-h" || flag === "--help") {
|
|
101
|
+
deps.stdout(USAGE);
|
|
102
|
+
return 0;
|
|
103
|
+
}
|
|
104
|
+
if (flag === "--dry-run") {
|
|
105
|
+
values.dryRun = true;
|
|
106
|
+
index += 1;
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (flag === "--")
|
|
110
|
+
break;
|
|
111
|
+
const key = aliases[flag];
|
|
112
|
+
if (key) {
|
|
113
|
+
const value = argv[index + 1] ?? "";
|
|
114
|
+
if ((key === "hqRoot" || key === "failureClass") && !value) {
|
|
115
|
+
deps.stderr(`hq-job-remediate: --${key === "hqRoot" ? "hq-root" : "failure-class"} requires ${key === "hqRoot" ? "a directory" : "a value"}\n`);
|
|
116
|
+
return 2;
|
|
117
|
+
}
|
|
118
|
+
values[key] = value;
|
|
119
|
+
index += 2;
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
if (flag.startsWith("-")) {
|
|
123
|
+
deps.stderr(`hq-job-remediate: unknown option: ${flag}\n`);
|
|
124
|
+
return 2;
|
|
125
|
+
}
|
|
126
|
+
deps.stderr(`hq-job-remediate: unexpected argument: ${flag}\n`);
|
|
127
|
+
return 2;
|
|
128
|
+
}
|
|
129
|
+
if (!values.failureClass) {
|
|
130
|
+
deps.stderr("hq-job-remediate: usage: hq-job-remediate.sh --failure-class <auth|secrets|timeout|agent_error|infra>\n");
|
|
131
|
+
return 2;
|
|
132
|
+
}
|
|
133
|
+
if (!["auth", "secrets", "timeout", "agent_error", "infra"].includes(values.failureClass)) {
|
|
134
|
+
deps.stderr(`hq-job-remediate: unknown failure_class: ${values.failureClass} (want auth|secrets|timeout|agent_error|infra)\n`);
|
|
135
|
+
return 2;
|
|
136
|
+
}
|
|
137
|
+
if (values.company && values.company !== "null" && !/^[a-z0-9]([a-z0-9_-]*[a-z0-9])?$/.test(values.company)) {
|
|
138
|
+
deps.stderr(`hq-job-remediate: invalid --company slug '${values.company}' (want lowercase kebab/underscore)\n`);
|
|
139
|
+
return 2;
|
|
140
|
+
}
|
|
141
|
+
return values;
|
|
142
|
+
}
|
|
143
|
+
function canUseLiveRootForArguments(argv) {
|
|
144
|
+
const parsed = parseArguments(argv, {
|
|
145
|
+
env: {},
|
|
146
|
+
stdout: () => undefined,
|
|
147
|
+
stderr: () => undefined,
|
|
148
|
+
});
|
|
149
|
+
return typeof parsed !== "number";
|
|
150
|
+
}
|
|
151
|
+
function globRegex(segment) {
|
|
152
|
+
let pattern = "^";
|
|
153
|
+
for (let index = 0; index < segment.length; index += 1) {
|
|
154
|
+
const char = segment[index];
|
|
155
|
+
if (char === "\\" && index + 1 < segment.length) {
|
|
156
|
+
pattern += segment[++index].replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
157
|
+
}
|
|
158
|
+
else if (char === "*")
|
|
159
|
+
pattern += ".*";
|
|
160
|
+
else if (char === "?")
|
|
161
|
+
pattern += ".";
|
|
162
|
+
else if (char === "[") {
|
|
163
|
+
const end = segment.indexOf("]", index + 1);
|
|
164
|
+
if (end < 0)
|
|
165
|
+
pattern += "\\[";
|
|
166
|
+
else {
|
|
167
|
+
let body = segment.slice(index + 1, end);
|
|
168
|
+
if (body.startsWith("!"))
|
|
169
|
+
body = `^${body.slice(1)}`;
|
|
170
|
+
pattern += `[${body.replace(/\\/g, "")} ]`.replace(" ]", "]");
|
|
171
|
+
index = end;
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
else
|
|
175
|
+
pattern += char.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
176
|
+
}
|
|
177
|
+
return new RegExp(`${pattern}$`);
|
|
178
|
+
}
|
|
179
|
+
/** Bash's unquoted array assignment splits on IFS, then expands pathname globs. */
|
|
180
|
+
function splitOverrideCommand(command, cwd) {
|
|
181
|
+
const words = command.split(/[ \t\n]+/).filter(Boolean);
|
|
182
|
+
const expanded = [];
|
|
183
|
+
for (const word of words) {
|
|
184
|
+
if (!/[?*[]/.test(word)) {
|
|
185
|
+
expanded.push(word);
|
|
186
|
+
continue;
|
|
187
|
+
}
|
|
188
|
+
const absolute = path.isAbsolute(word);
|
|
189
|
+
const parts = word.split(path.sep);
|
|
190
|
+
let prefixes = [absolute ? path.sep : ""];
|
|
191
|
+
for (const part of parts) {
|
|
192
|
+
if (!part)
|
|
193
|
+
continue;
|
|
194
|
+
if (!/[?*[]/.test(part)) {
|
|
195
|
+
prefixes = prefixes.map((prefix) => path.join(prefix || cwd, part));
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
const matcher = globRegex(part);
|
|
199
|
+
const next = [];
|
|
200
|
+
for (const prefix of prefixes) {
|
|
201
|
+
const directory = prefix || cwd;
|
|
202
|
+
let names;
|
|
203
|
+
try {
|
|
204
|
+
names = fs.readdirSync(directory);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
for (const name of names) {
|
|
210
|
+
if (!part.startsWith(".") && name.startsWith("."))
|
|
211
|
+
continue;
|
|
212
|
+
if (matcher.test(name))
|
|
213
|
+
next.push(path.join(directory, name));
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
prefixes = next;
|
|
217
|
+
if (!prefixes.length)
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
if (!prefixes.length)
|
|
221
|
+
expanded.push(word);
|
|
222
|
+
else
|
|
223
|
+
expanded.push(...prefixes.map((entry) => absolute ? entry : path.relative(cwd, entry) || "."));
|
|
224
|
+
}
|
|
225
|
+
return expanded;
|
|
226
|
+
}
|
|
227
|
+
const UNSAFE_OVERRIDE = /[;|&$`()<>]/;
|
|
228
|
+
export function runJobRemediate(argv, options = {}) {
|
|
229
|
+
const deps = {
|
|
230
|
+
...defaultJobRemediateDependencies(options.dependencies?.env ?? process.env, options.dependencies?.cwd ?? process.cwd()),
|
|
231
|
+
...options.dependencies,
|
|
232
|
+
};
|
|
233
|
+
if (options.hqRoot)
|
|
234
|
+
deps.env = { ...deps.env, HQ_ROOT: options.hqRoot };
|
|
235
|
+
const parsed = parseArguments(argv, deps);
|
|
236
|
+
if (typeof parsed === "number")
|
|
237
|
+
return parsed;
|
|
238
|
+
const hqRoot = parsed.hqRoot || options.hqRoot || deps.env.HQ_ROOT || "";
|
|
239
|
+
if (hqRoot)
|
|
240
|
+
deps.env = { ...deps.env, HQ_ROOT: hqRoot };
|
|
241
|
+
const dryRun = parsed.dryRun || deps.env.HQ_JOB_REMEDIATE_DRY === "1";
|
|
242
|
+
const actions = [];
|
|
243
|
+
let attempted = false;
|
|
244
|
+
let retryRecommended = false;
|
|
245
|
+
let alwaysHuman = false;
|
|
246
|
+
let nextAction = "";
|
|
247
|
+
let notes = "";
|
|
248
|
+
const logPath = deps.env.HQ_JOB_REMEDIATE_LOG || os.devNull;
|
|
249
|
+
const log = (message) => deps.stderr(`hq-job-remediate: ${message}\n`);
|
|
250
|
+
const emit = (code) => {
|
|
251
|
+
if (code === 2)
|
|
252
|
+
return 2;
|
|
253
|
+
deps.stdout(serializeJqJson({
|
|
254
|
+
failure_class: parsed.failureClass,
|
|
255
|
+
job_id: parsed.jobId,
|
|
256
|
+
attempted,
|
|
257
|
+
actions,
|
|
258
|
+
retry_recommended: retryRecommended,
|
|
259
|
+
always_human: alwaysHuman,
|
|
260
|
+
next_action: nextAction,
|
|
261
|
+
notes,
|
|
262
|
+
}, "compact"));
|
|
263
|
+
return 0;
|
|
264
|
+
};
|
|
265
|
+
const runOrDry = (label, command, args) => {
|
|
266
|
+
actions.push(label);
|
|
267
|
+
if (dryRun) {
|
|
268
|
+
log(`dry-run: would run: ${[command, ...args].join(" ")}`);
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
return deps.run(command, args, { cwd: deps.cwd, env: deps.env, logPath }) === 0;
|
|
272
|
+
};
|
|
273
|
+
const runOverrideOrArgv = (label, overrideOrCommand, args = []) => {
|
|
274
|
+
let command = overrideOrCommand;
|
|
275
|
+
let commandArgs = args;
|
|
276
|
+
if (args.length === 0 && overrideOrCommand.includes(" ")) {
|
|
277
|
+
if (UNSAFE_OVERRIDE.test(overrideOrCommand)) {
|
|
278
|
+
deps.stderr("hq-job-remediate: unsafe remediate override command rejected\n");
|
|
279
|
+
return 2;
|
|
280
|
+
}
|
|
281
|
+
[command, ...commandArgs] = splitOverrideCommand(overrideOrCommand, deps.cwd);
|
|
282
|
+
if (!command)
|
|
283
|
+
return true;
|
|
284
|
+
}
|
|
285
|
+
return runOrDry(label, command, commandArgs);
|
|
286
|
+
};
|
|
287
|
+
const resolveAuthRefresh = () => {
|
|
288
|
+
const override = deps.env.HQ_JOB_REMEDIATE_AUTH_REFRESH_CMD || "";
|
|
289
|
+
if (override)
|
|
290
|
+
return override;
|
|
291
|
+
if (deps.commandExists("hq"))
|
|
292
|
+
return "hq auth refresh";
|
|
293
|
+
if (deps.commandExists("hq-auth-refresh"))
|
|
294
|
+
return "hq-auth-refresh";
|
|
295
|
+
return "";
|
|
296
|
+
};
|
|
297
|
+
switch (parsed.failureClass) {
|
|
298
|
+
case "secrets":
|
|
299
|
+
alwaysHuman = true;
|
|
300
|
+
nextAction = "share the missing secret to this Outpost (hq secrets share KEY) then re-probe";
|
|
301
|
+
notes = "secret share is always-human; no self-remediation";
|
|
302
|
+
return emit(0);
|
|
303
|
+
case "auth": {
|
|
304
|
+
alwaysHuman = true;
|
|
305
|
+
nextAction = "re-login on Outpost (device code) for the job runtime CLI";
|
|
306
|
+
notes = "attempt Cognito refresh if available; never loop device re-login";
|
|
307
|
+
const command = resolveAuthRefresh();
|
|
308
|
+
if (command) {
|
|
309
|
+
attempted = true;
|
|
310
|
+
const result = runOverrideOrArgv("cognito_token_refresh", command);
|
|
311
|
+
if (result === 2)
|
|
312
|
+
return 2;
|
|
313
|
+
if (result)
|
|
314
|
+
notes = "cognito refresh ok; device re-login still required if CLI creds missing";
|
|
315
|
+
else
|
|
316
|
+
notes = "cognito refresh failed or unavailable; device re-login required";
|
|
317
|
+
}
|
|
318
|
+
else
|
|
319
|
+
notes = "no hq auth refresh on PATH; device re-login required";
|
|
320
|
+
return emit(0);
|
|
321
|
+
}
|
|
322
|
+
case "timeout":
|
|
323
|
+
nextAction = "raise timeout_seconds or split the job work";
|
|
324
|
+
notes = "timeout is not self-retried in v1 (avoid thrash)";
|
|
325
|
+
return emit(0);
|
|
326
|
+
case "agent_error":
|
|
327
|
+
nextAction = "inspect the job log and fix the prompt/skill";
|
|
328
|
+
notes = "agent_error is not self-remediated in v1";
|
|
329
|
+
return emit(0);
|
|
330
|
+
case "infra": {
|
|
331
|
+
nextAction = "retry after sync; if it persists, check Outpost disk/network";
|
|
332
|
+
notes = "allowlist: cognito refresh + one hq-sync pull + single retry";
|
|
333
|
+
const authCommand = resolveAuthRefresh();
|
|
334
|
+
if (authCommand) {
|
|
335
|
+
attempted = true;
|
|
336
|
+
const result = runOverrideOrArgv("cognito_token_refresh", authCommand);
|
|
337
|
+
if (result === 2)
|
|
338
|
+
return 2;
|
|
339
|
+
}
|
|
340
|
+
const syncOverride = deps.env.HQ_JOB_REMEDIATE_SYNC_PULL_CMD || "";
|
|
341
|
+
attempted = true;
|
|
342
|
+
if (syncOverride) {
|
|
343
|
+
const result = runOverrideOrArgv("hq_sync_pull", syncOverride);
|
|
344
|
+
if (result === 2)
|
|
345
|
+
return 2;
|
|
346
|
+
retryRecommended = true;
|
|
347
|
+
notes = result ? "sync pull ok; recommend single immediate retry" : "sync pull failed; still recommend one infra retry";
|
|
348
|
+
}
|
|
349
|
+
else if (deps.commandExists("hq")) {
|
|
350
|
+
const syncArgs = parsed.company && parsed.company !== "null"
|
|
351
|
+
? ["sync", "pull", "--company", parsed.company, "--hq-root", hqRoot]
|
|
352
|
+
: ["sync", "pull", "--personal", "--hq-root", hqRoot];
|
|
353
|
+
const result = runOrDry("hq_sync_pull", "hq", syncArgs);
|
|
354
|
+
retryRecommended = true;
|
|
355
|
+
notes = result ? "sync pull ok; recommend single immediate retry" : "sync pull failed; still recommend one infra retry";
|
|
356
|
+
}
|
|
357
|
+
else {
|
|
358
|
+
actions.push("hq_sync_pull_unavailable");
|
|
359
|
+
retryRecommended = true;
|
|
360
|
+
notes = "hq sync pull unavailable; recommend single infra retry anyway";
|
|
361
|
+
}
|
|
362
|
+
return emit(0);
|
|
363
|
+
}
|
|
364
|
+
default:
|
|
365
|
+
// parseArguments rejects unknown classes before any remediation path.
|
|
366
|
+
return 2;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
export function registerCoreJobRemediateNativeCommand(group, core) {
|
|
370
|
+
registerCoreJobLeaf(group, core, "remediate", ASSET, "Run bounded remediation for a scheduled-job failure", (argv, hqRoot) => runJobRemediate(argv, { hqRoot }), canUseLiveRootForArguments, [
|
|
371
|
+
"--hq-root", "--failure-class", "--job-id", "--company",
|
|
372
|
+
], ["--dry-run"]);
|
|
373
|
+
}
|
|
374
|
+
//# sourceMappingURL=core-job-remediate-native.js.map
|
package/dist/commands/core.js
CHANGED
|
@@ -40,7 +40,7 @@ import { registerCoreJobsNativeCommand } from "./core-job-native.js";
|
|
|
40
40
|
import { registerCoreCheckpointGateSettingCommand } from "./core-checkpoint-gate-setting.js";
|
|
41
41
|
import { resolveLiveRoot } from "../utils/hq-roots.js";
|
|
42
42
|
import { interpreterFor, runBundledScript } from "../utils/run-bundled-script.js";
|
|
43
|
-
import { renderIndexTarget } from "../lib/index-render/index.js";
|
|
43
|
+
import { CompanyIndexUserError, renderIndexTarget } from "../lib/index-render/index.js";
|
|
44
44
|
import { generateWorkersRegistry } from "../lib/workers-registry/index.js";
|
|
45
45
|
import { scanPackages } from "../lib/scan-packages/index.js";
|
|
46
46
|
import { detectStaleCorePolicyMirror } from "../lib/core-utils/detect-stale-core-policy-mirror.js";
|
|
@@ -57,6 +57,7 @@ import { timeoutGuardCommand } from "../lib/core-utils/timeout-guard.js";
|
|
|
57
57
|
import { readSentryReportStdin, reportSentryEvent } from "../lib/core-utils/sentry-report.js";
|
|
58
58
|
import { CLEAN_HANDOFF_ASSET, RESUME_LOCK_ASSET, VERIFY_DELIVERABLES_ASSET, WORKTREE_GC_ASSET, runCleanWorktreeReconcileHandoff, runResumeThreadLock, runVerifyStoryDeliverables, runWorktreeGc, } from "../lib/core-utils/worktree-resume.js";
|
|
59
59
|
import { coreNativeHandoffEnabled } from "./core-native-handoff-flag.js";
|
|
60
|
+
import { coreCompanyIndexScopeEnabled } from "./core-company-index-scope-flag.js";
|
|
60
61
|
import { describeCoreCommand, registeredCoreCommandListing, } from "./core-command-catalog.js";
|
|
61
62
|
/**
|
|
62
63
|
* These commands are now native implementations. Their retained shell assets
|
|
@@ -489,18 +490,35 @@ export function registerCoreCommands(program) {
|
|
|
489
490
|
throw expectedUserError(`Unknown rebuild-index target "${target}". Valid targets: ${validTargets.join(", ")}.`);
|
|
490
491
|
}
|
|
491
492
|
const scope = core.opts();
|
|
492
|
-
const hqRoot = resolveLiveRoot({ hqRoot: scope.hqRoot });
|
|
493
493
|
const operands = cmd.args.length > 0 ? cmd.args : args;
|
|
494
494
|
const passthrough = operands.slice(1);
|
|
495
495
|
const force = passthrough.includes("--accept");
|
|
496
496
|
const rendererArgs = passthrough.filter((arg) => arg !== "--accept");
|
|
497
|
+
const isCompaniesTarget = entry.renderer === "companies";
|
|
498
|
+
const wantsHelp = rendererArgs.some((arg) => arg === "--help" || arg === "-h");
|
|
499
|
+
if (isCompaniesTarget && wantsHelp) {
|
|
500
|
+
renderIndexTarget(entry.renderer, { root: "", output: (message) => process.stdout.write(message) }, rendererArgs);
|
|
501
|
+
return;
|
|
502
|
+
}
|
|
503
|
+
const requestsCompanyScope = isCompaniesTarget && rendererArgs.some((arg) => arg === "--company" || arg.startsWith("--company="));
|
|
504
|
+
if (requestsCompanyScope && !coreCompanyIndexScopeEnabled()) {
|
|
505
|
+
throw expectedUserError("rebuild-index companies --company is disabled; enable cli.rebuild-index-company-scope for a canary.");
|
|
506
|
+
}
|
|
507
|
+
const hqRoot = resolveLiveRoot({ hqRoot: scope.hqRoot });
|
|
497
508
|
const refused = [];
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
509
|
+
try {
|
|
510
|
+
renderIndexTarget(entry.renderer, {
|
|
511
|
+
root: hqRoot,
|
|
512
|
+
log: (message) => process.stderr.write(`${message}\n`),
|
|
513
|
+
force,
|
|
514
|
+
refused,
|
|
515
|
+
}, rendererArgs);
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
if (error instanceof CompanyIndexUserError)
|
|
519
|
+
throw expectedUserError(error.message);
|
|
520
|
+
throw error;
|
|
521
|
+
}
|
|
504
522
|
if (refused.length > 0) {
|
|
505
523
|
throw expectedUserError(`rebuild-index: refused to overwrite ${refused.length} hand-edited INDEX.md file(s). Re-run with --accept to overwrite.`);
|
|
506
524
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { deployApiRequest } from "../lib/deploy-api.js";
|
|
2
|
+
import { listDeployApps } from "../lib/deploy-apps.js";
|
|
2
3
|
import { DEFAULT_HQ_ROOT, ensureCognitoToken } from "../utils/cognito-session.js";
|
|
3
4
|
import { readActiveCompanySlug } from "./skill.js";
|
|
4
5
|
class DeployCommentsError extends Error {
|
|
@@ -50,8 +51,10 @@ async function deployJson(token, route, orgHeaders) {
|
|
|
50
51
|
}
|
|
51
52
|
}
|
|
52
53
|
async function resolveAppId(token, appNameOrId, orgHeaders) {
|
|
53
|
-
const
|
|
54
|
-
|
|
54
|
+
const apps = await listDeployApps((cursor) => {
|
|
55
|
+
const route = cursor ? `/api/apps?cursor=${encodeURIComponent(cursor)}` : "/api/apps";
|
|
56
|
+
return deployJson(token, route, orgHeaders);
|
|
57
|
+
});
|
|
55
58
|
const byId = apps.find((app) => app && typeof app.id === "string" && app.id === appNameOrId);
|
|
56
59
|
const matches = byId
|
|
57
60
|
? [byId]
|
package/dist/commands/files.js
CHANGED
|
@@ -14,6 +14,7 @@ import { findHqRoot } from "../utils/manifest.js";
|
|
|
14
14
|
import { maxLength } from "../utils/table-width.js";
|
|
15
15
|
import { resolveCompanyFlagGate } from "../lib/flag-registry.js";
|
|
16
16
|
const PERSON_UID_PATTERN = /^(prs|agt)_[A-Za-z0-9_-]+$/;
|
|
17
|
+
const AGENT_UID_PATTERN = /^agt_[A-Za-z0-9_-]+$/;
|
|
17
18
|
export const FILES_SHARE_EMPTY_PREFIX_WARNING_FLAG = "files.share-empty-prefix-warning";
|
|
18
19
|
const FILES_SHARE_EMPTY_PREFIX_CHECK_TIMEOUT_MS = 5_000;
|
|
19
20
|
function collectMentions(value, previous) {
|
|
@@ -265,14 +266,15 @@ export function registerFilesCommand(program) {
|
|
|
265
266
|
files
|
|
266
267
|
.command("unshare <prefix>")
|
|
267
268
|
.description("Remove a file access grant. A trailing slash targets the private-folder row exactly.")
|
|
268
|
-
.requiredOption("--with <principal>", "Person uid, email address, group id, or '@all' to remove the grant")
|
|
269
|
+
.requiredOption("--with <principal>", "Person uid, agent uid, email address, group id, or '@all' to remove the grant")
|
|
269
270
|
.action(async (prefix, opts) => {
|
|
270
271
|
try {
|
|
271
|
-
// Unshare MUTATES access with no preview
|
|
272
|
-
//
|
|
273
|
-
//
|
|
272
|
+
// Unshare MUTATES access with no preview. Anchored content paths remain
|
|
273
|
+
// refused; the exact company-root ACL row is the sole exception and
|
|
274
|
+
// resolves its anchor against the selected company before revoking.
|
|
274
275
|
const canonicalPrefix = normalizeCompanyFilePrefix(prefix, {
|
|
275
276
|
mutating: true,
|
|
277
|
+
allowCompanyRootAclRow: true,
|
|
276
278
|
...(files.opts().company !== undefined
|
|
277
279
|
? { companySlug: files.opts().company }
|
|
278
280
|
: {}),
|
|
@@ -281,11 +283,14 @@ export function registerFilesCommand(program) {
|
|
|
281
283
|
const isAll = principal === "@all";
|
|
282
284
|
const isEmail = !isAll && EMAIL_PATTERN.test(principal);
|
|
283
285
|
const isGroup = !isAll && GROUP_ID_PATTERN.test(principal);
|
|
284
|
-
const
|
|
285
|
-
|
|
286
|
-
|
|
286
|
+
const isAgent = !isAll && !isEmail && !isGroup && AGENT_UID_PATTERN.test(principal);
|
|
287
|
+
const isPerson = !isAll && !isEmail && !isGroup && !isAgent && PERSON_UID_PATTERN.test(principal);
|
|
288
|
+
if (!isAll && !isEmail && !isGroup && !isPerson && !isAgent) {
|
|
289
|
+
console.error(chalk.red(`Invalid principal '${principal}': must be '@all', an email address, a person uid matching prs_<alphanumeric>, an agent uid matching agt_<alphanumeric>, or a group id matching grp_<alphanumeric>`));
|
|
287
290
|
process.exit(1);
|
|
288
291
|
}
|
|
292
|
+
// The vault ACL API stores first-class agent access on its backwards-
|
|
293
|
+
// compatible person tuple: { granteeType: "person", granteeId: "agt_*" }.
|
|
289
294
|
const granteeType = isAll
|
|
290
295
|
? "company-wide"
|
|
291
296
|
: isEmail
|
|
@@ -300,8 +305,14 @@ export function registerFilesCommand(program) {
|
|
|
300
305
|
: principal;
|
|
301
306
|
const principalLabel = isAll ? "everyone in the company" : granteeId;
|
|
302
307
|
const token = (await resolveVaultCredential()).token;
|
|
303
|
-
const
|
|
304
|
-
const
|
|
308
|
+
const explicitCompanySlug = files.opts().company;
|
|
309
|
+
const companyRootSlug = stripRedundantCompanyScope(prefix)?.prefix === ""
|
|
310
|
+
? companySlugFromAnchor(prefix)
|
|
311
|
+
: undefined;
|
|
312
|
+
const companyUid = await resolveCompanyUidForAnchoredPath(token, {
|
|
313
|
+
explicit: explicitCompanySlug,
|
|
314
|
+
anchorSlug: companyRootSlug,
|
|
315
|
+
});
|
|
305
316
|
const res = await vaultApiFetch({
|
|
306
317
|
token,
|
|
307
318
|
path: `/files/${encodeURIComponent(companyUid)}/acl/revoke`,
|
|
@@ -361,6 +372,7 @@ export function registerFilesCommand(program) {
|
|
|
361
372
|
// a DIFFERENT company's ACL than the one the operator pasted.
|
|
362
373
|
const explicitCompany = files.opts().company;
|
|
363
374
|
const canonicalPrefix = normalizeCompanyFilePrefix(prefix, {
|
|
375
|
+
allowCompanyRootAclRow: true,
|
|
364
376
|
...(explicitCompany !== undefined
|
|
365
377
|
? { companySlug: explicitCompany }
|
|
366
378
|
: {}),
|
|
@@ -1085,9 +1097,11 @@ export function companySlugFromAnchor(prefix, personal) {
|
|
|
1085
1097
|
* Normalize company paths at the CLI boundary, preserving personal keys.
|
|
1086
1098
|
*
|
|
1087
1099
|
* `mutating` marks a route that CHANGES access and has no preview or
|
|
1088
|
-
* confirmation step (grant, share, unshare). Those refuse
|
|
1089
|
-
*
|
|
1100
|
+
* confirmation step (grant, share, unshare). Those refuse anchored content
|
|
1101
|
+
* paths rather than guessing, because stripping the anchor changes which
|
|
1090
1102
|
* objects the operation reaches and there is nothing downstream to catch it.
|
|
1103
|
+
* `allowCompanyRootAclRow` is limited to exact-root ACL inspect/revoke calls:
|
|
1104
|
+
* those need the original anchor to match a legacy company-root ACL row.
|
|
1091
1105
|
*
|
|
1092
1106
|
* `companySlug` is the company the command is actually targeting. When the
|
|
1093
1107
|
* anchor names a DIFFERENT company we refuse in every mode: stripping would
|
|
@@ -1115,9 +1129,12 @@ function normalizeCompanyFilePrefix(prefix, options = {}) {
|
|
|
1115
1129
|
`operation to '${options.companySlug}' instead. Re-run with ` +
|
|
1116
1130
|
`--company ${scope.strippedSlug}, or pass the bucket-relative path.`);
|
|
1117
1131
|
}
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1132
|
+
if (!scope.prefix && options.allowCompanyRootAclRow) {
|
|
1133
|
+
return normalizeFilePrefix(prefix);
|
|
1134
|
+
}
|
|
1135
|
+
// A bare anchor strips to nothing. Content operations must fail with a
|
|
1136
|
+
// message that says what to do instead; exact-root ACL operations preserve
|
|
1137
|
+
// the original anchor above. `deferRootReject` is for callers that already have a
|
|
1121
1138
|
// root guard with better copy — `runFilesDelete` says "Refusing to delete the
|
|
1122
1139
|
// vault root", which beats anything this generic helper could write.
|
|
1123
1140
|
if (!scope.prefix && !options.deferRootReject) {
|
|
@@ -163,15 +163,15 @@ export interface InstallInput {
|
|
|
163
163
|
};
|
|
164
164
|
}
|
|
165
165
|
export declare function installIntegration(token: string, companyUid: string, input: InstallInput): Promise<InstallResult>;
|
|
166
|
-
export declare function uninstallIntegration(token: string, companyUid: string,
|
|
166
|
+
export declare function uninstallIntegration(token: string, companyUid: string, target: {
|
|
167
167
|
installationId: string;
|
|
168
|
+
} | {
|
|
169
|
+
connectionId: string;
|
|
170
|
+
}): Promise<{
|
|
171
|
+
installationId: string | null;
|
|
168
172
|
connectionId: string;
|
|
169
173
|
}>;
|
|
170
|
-
/**
|
|
171
|
-
* Permanently remove a revoked connection tombstone. The admin purge endpoint
|
|
172
|
-
* deliberately accepts only the connection id: it derives the company and
|
|
173
|
-
* enforces owner authorization from the authenticated connection record.
|
|
174
|
-
*/
|
|
174
|
+
/** Permanently remove a revoked connection tombstone for one company. */
|
|
175
175
|
export declare function purgeConnection(token: string, companyUid: string, connectionId: string): Promise<{
|
|
176
176
|
connectionId: string;
|
|
177
177
|
}>;
|
|
@@ -74,33 +74,24 @@ export async function installIntegration(token, companyUid, input) {
|
|
|
74
74
|
}
|
|
75
75
|
return (await res.json());
|
|
76
76
|
}
|
|
77
|
-
export async function uninstallIntegration(token, companyUid,
|
|
77
|
+
export async function uninstallIntegration(token, companyUid, target) {
|
|
78
78
|
const res = await vaultApiFetch({
|
|
79
79
|
token,
|
|
80
80
|
path: "/v1/integrations/factory/uninstall",
|
|
81
81
|
method: "POST",
|
|
82
|
-
body: { companyUid,
|
|
82
|
+
body: { companyUid, ...target },
|
|
83
83
|
});
|
|
84
84
|
if (!res.ok)
|
|
85
85
|
await raiseForResponse(res, "Failed to disconnect the app");
|
|
86
86
|
return (await res.json());
|
|
87
87
|
}
|
|
88
|
-
/**
|
|
89
|
-
* Permanently remove a revoked connection tombstone. The admin purge endpoint
|
|
90
|
-
* deliberately accepts only the connection id: it derives the company and
|
|
91
|
-
* enforces owner authorization from the authenticated connection record.
|
|
92
|
-
*/
|
|
88
|
+
/** Permanently remove a revoked connection tombstone for one company. */
|
|
93
89
|
export async function purgeConnection(token, companyUid, connectionId) {
|
|
94
|
-
// Keep the company in this client's call signature with the other
|
|
95
|
-
// connection mutations. It is resolved before the target connection so a
|
|
96
|
-
// caller cannot use a slug from a different company, but the server's purge
|
|
97
|
-
// contract intentionally takes only connectionId in its body.
|
|
98
|
-
void companyUid;
|
|
99
90
|
const res = await vaultApiFetch({
|
|
100
91
|
token,
|
|
101
92
|
path: "/v1/integrations/admin/purge",
|
|
102
93
|
method: "POST",
|
|
103
|
-
body: { connectionId },
|
|
94
|
+
body: { companyUid, connectionId },
|
|
104
95
|
});
|
|
105
96
|
if (!res.ok)
|
|
106
97
|
await raiseForResponse(res, "Failed to purge the revoked connection");
|
|
@@ -30,6 +30,7 @@ interface ConnectOptions {
|
|
|
30
30
|
displayName?: string;
|
|
31
31
|
token?: string;
|
|
32
32
|
tokenStdin?: boolean;
|
|
33
|
+
authHeader?: string;
|
|
33
34
|
auth?: string;
|
|
34
35
|
browser?: boolean;
|
|
35
36
|
timeout?: string;
|
|
@@ -48,6 +49,8 @@ interface ConnectOptions {
|
|
|
48
49
|
* than fall through to a default.
|
|
49
50
|
*/
|
|
50
51
|
export declare function assertAuthMode(value: string | undefined): void;
|
|
52
|
+
/** RFC 7230 token characters accepted for an HTTP field name. */
|
|
53
|
+
export declare function assertAuthHeader(value: string | undefined): void;
|
|
51
54
|
/** Single-quote a value for a copy-pasteable shell command. */
|
|
52
55
|
export declare function shellQuote(value: string): string;
|
|
53
56
|
/** Everything the connect verbs need to name one app to hq-pro. */
|