@indigoai-us/hq-cli 5.345.156 → 5.345.159
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 +231 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +110 -2
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +101 -5
- package/assets/scaffold/core/scripts/worktree.sh +4 -0
- package/dist/command-catalog.generated.d.ts +531 -6
- package/dist/command-catalog.generated.js +674 -27
- package/dist/command-registration-plan.d.ts +18 -0
- package/dist/command-registration-plan.js +4 -0
- package/dist/commands/_patterns.d.ts +17 -0
- package/dist/commands/_patterns.js +22 -0
- package/dist/commands/agent-kit.js +8 -6
- package/dist/commands/bot.js +7 -4
- package/dist/commands/channel-suggest.d.ts +56 -0
- package/dist/commands/channel-suggest.js +170 -0
- package/dist/commands/channels.js +7 -1
- package/dist/commands/cloud.d.ts +45 -0
- package/dist/commands/cloud.js +150 -10
- package/dist/commands/company.js +4 -3
- package/dist/commands/core-checkpoint-native.d.ts +18 -0
- package/dist/commands/core-checkpoint-native.js +67 -2
- package/dist/commands/core-pipeline-driver-native.d.ts +12 -1
- package/dist/commands/core-pipeline-driver-native.js +131 -5
- package/dist/commands/core.js +12 -0
- package/dist/commands/crm.js +83 -6
- package/dist/commands/deploy-access.d.ts +33 -0
- package/dist/commands/deploy-access.js +322 -0
- package/dist/commands/dm-attach.d.ts +106 -0
- package/dist/commands/dm-attach.js +184 -0
- package/dist/commands/dm.d.ts +37 -0
- package/dist/commands/dm.js +172 -10
- package/dist/commands/feedback-show.d.ts +40 -0
- package/dist/commands/feedback-show.js +261 -0
- package/dist/commands/feedback.d.ts +21 -0
- package/dist/commands/feedback.js +152 -2
- package/dist/commands/files.d.ts +24 -0
- package/dist/commands/files.js +52 -1
- package/dist/commands/integrations-connect.js +7 -3
- package/dist/commands/lanes-accounts.js +16 -1
- package/dist/commands/lanes-loop.js +14 -4
- package/dist/commands/lanes-message.js +6 -4
- package/dist/commands/lanes-resume.js +36 -13
- package/dist/commands/lanes-shared.d.ts +5 -0
- package/dist/commands/lanes-shared.js +13 -3
- package/dist/commands/lanes-watch.js +1 -0
- package/dist/commands/lanes.d.ts +20 -1
- package/dist/commands/lanes.js +201 -51
- package/dist/commands/mesh.js +149 -1
- package/dist/commands/monitor.d.ts +1 -0
- package/dist/commands/monitor.js +4 -1
- package/dist/commands/repo.d.ts +41 -0
- package/dist/commands/repo.js +331 -0
- package/dist/commands/scan.d.ts +3 -1
- package/dist/commands/scan.js +7 -3
- package/dist/commands/secrets.d.ts +21 -0
- package/dist/commands/secrets.js +114 -14
- package/dist/commands/signals.js +17 -5
- package/dist/commands/staff/listings.d.ts +41 -0
- package/dist/commands/staff/listings.js +160 -0
- package/dist/commands/staff/reset-test-account.d.ts +3 -2
- package/dist/commands/staff/reset-test-account.js +4 -3
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2629 -1036
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/run/inbox.js +2 -1
- package/dist/lib/agent-kit/wake-secrets.d.ts +7 -0
- package/dist/lib/agent-kit/wake-secrets.js +32 -0
- package/dist/lib/agent-kit/wake.d.ts +4 -0
- package/dist/lib/agent-kit/wake.js +49 -3
- package/dist/lib/bot/inflight.d.ts +7 -1
- package/dist/lib/bot/inflight.js +10 -2
- package/dist/lib/bot/prompt.js +3 -0
- package/dist/lib/bot/run.js +72 -20
- package/dist/lib/bot/session.d.ts +14 -0
- package/dist/lib/bot/session.js +70 -0
- package/dist/lib/capacity/collectors/claude-oauth.d.ts +6 -0
- package/dist/lib/capacity/collectors/claude-oauth.js +12 -0
- package/dist/lib/capacity/lane-login.d.ts +6 -0
- package/dist/lib/capacity/lane-login.js +5 -0
- package/dist/lib/core-utils/worktree.js +2 -0
- package/dist/lib/daemon/exit-cause.d.ts +43 -0
- package/dist/lib/daemon/exit-cause.js +88 -0
- package/dist/lib/daemon/handoff.d.ts +2 -0
- package/dist/lib/daemon/handoff.js +6 -1
- package/dist/lib/daemon/hq-child-bootstrap.mjs +20 -3
- package/dist/lib/daemon/paths.d.ts +2 -0
- package/dist/lib/daemon/paths.js +1 -0
- package/dist/lib/daemon/run.js +13 -0
- package/dist/lib/daemon/supervisor.d.ts +54 -1
- package/dist/lib/daemon/supervisor.js +297 -21
- package/dist/lib/daemon/unit-holds.d.ts +22 -0
- package/dist/lib/daemon/unit-holds.js +86 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/index-render/company-knowledge.js +1 -3
- package/dist/lib/index-render/projects.js +1 -3
- package/dist/lib/lanes/account-company.d.ts +28 -0
- package/dist/lib/lanes/account-company.js +53 -0
- package/dist/lib/lanes/accounts.d.ts +22 -3
- package/dist/lib/lanes/accounts.js +38 -2
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +6 -1
- package/dist/lib/lanes/claude-credential.d.ts +78 -0
- package/dist/lib/lanes/claude-credential.js +163 -0
- package/dist/lib/lanes/compaction.d.ts +2 -0
- package/dist/lib/lanes/compaction.js +9 -0
- package/dist/lib/lanes/config.js +6 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +2 -1
- package/dist/lib/lanes/hooks-install.js +2 -1
- package/dist/lib/lanes/host-capacity.d.ts +2 -0
- package/dist/lib/lanes/host-capacity.js +1 -0
- package/dist/lib/lanes/keep-alive.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.js +3 -1
- package/dist/lib/lanes/links.js +10 -4
- package/dist/lib/lanes/loop.js +16 -1
- package/dist/lib/lanes/node-binary.d.ts +17 -0
- package/dist/lib/lanes/node-binary.js +30 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
- package/dist/lib/lanes/outpost-dispatch.js +1 -0
- package/dist/lib/lanes/reconcile.js +4 -0
- package/dist/lib/lanes/remote-control-lane.js +2 -0
- package/dist/lib/lanes/spawn.d.ts +3 -0
- package/dist/lib/lanes/spawn.js +119 -9
- package/dist/lib/lanes/store.d.ts +6 -0
- package/dist/lib/lanes/store.js +107 -18
- package/dist/lib/lanes/types.d.ts +2 -0
- package/dist/lib/lanes/watch.js +28 -5
- package/dist/lib/lanes/watcher-main.js +4 -3
- package/dist/lib/lanes/watcher.d.ts +4 -0
- package/dist/lib/lanes/watcher.js +110 -21
- package/dist/lib/mesh/api.d.ts +32 -0
- package/dist/lib/mesh/api.js +75 -0
- package/dist/lib/secret-scan/detect.js +9 -0
- package/dist/lib/secret-scan/repo-config.d.ts +41 -0
- package/dist/lib/secret-scan/repo-config.js +237 -0
- package/dist/lib/secret-scan/rules.d.ts +7 -1
- package/dist/lib/secret-scan/rules.js +3 -1
- package/dist/lib/secret-scan/staged.d.ts +5 -0
- package/dist/lib/secret-scan/staged.js +19 -2
- package/dist/lib/secret-scan/types.d.ts +2 -0
- package/dist/lib/sync/server-owned-local-only.d.ts +29 -0
- package/dist/lib/sync/server-owned-local-only.js +75 -0
- package/dist/utils/package-use-lease.js +27 -9
- package/dist/utils/version-gate.d.ts +7 -0
- package/dist/utils/version-gate.js +66 -0
- package/package.json +8 -8
package/dist/commands/cloud.js
CHANGED
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { isAccessDeniedError, accessDeniedKeyOf, formatAccessDenied, } from "../utils/access-denied-hint.js";
|
|
16
16
|
import { findReadOnlyLocalOnly, isGrantEnforced, readVaultAccessManifest, } from "../lib/sync/read-only-local-only.js";
|
|
17
|
+
import { SERVER_OWNED_FOLDERS, findServerOwnedLocalOnly, } from "../lib/sync/server-owned-local-only.js";
|
|
17
18
|
import chalk from "chalk";
|
|
18
19
|
import * as fs from "fs";
|
|
19
20
|
import * as path from "path";
|
|
@@ -165,6 +166,37 @@ export function staleUploadRefusalLines(count, paths = []) {
|
|
|
165
166
|
}
|
|
166
167
|
return lines;
|
|
167
168
|
}
|
|
169
|
+
export function readUploadFailures(result) {
|
|
170
|
+
const r = result;
|
|
171
|
+
return { count: r.filesUploadFailed ?? 0, paths: r.uploadFailedPaths ?? [] };
|
|
172
|
+
}
|
|
173
|
+
/**
|
|
174
|
+
* Files the vault accepted in the plan but refused or failed at upload
|
|
175
|
+
* (feedback_943456f4). Without these lines a push that uploaded nothing
|
|
176
|
+
* printed "✓ Pushed 0" and exited 0.
|
|
177
|
+
*/
|
|
178
|
+
export function uploadFailedLines(count, paths = []) {
|
|
179
|
+
if (count <= 0)
|
|
180
|
+
return [];
|
|
181
|
+
const lines = [
|
|
182
|
+
`⚠ ${count} file(s) were NOT uploaded because the upload failed. ` +
|
|
183
|
+
`They are still on this machine. Push again to retry them.`,
|
|
184
|
+
];
|
|
185
|
+
if (paths.length > 0) {
|
|
186
|
+
lines.push(" Not uploaded:", ...paths.map((filePath) => ` - ${filePath}`));
|
|
187
|
+
}
|
|
188
|
+
return lines;
|
|
189
|
+
}
|
|
190
|
+
/**
|
|
191
|
+
* share() option that turns a user-named path an ignore rule drops into an
|
|
192
|
+
* "unreachable path" error instead of a silent 0-file success
|
|
193
|
+
* (feedback_63bfc302, feedback_8c294805). Spread as a plain record so this
|
|
194
|
+
* builds against an hq-cloud release whose ShareOptions predates the field;
|
|
195
|
+
* older releases ignore it.
|
|
196
|
+
*/
|
|
197
|
+
export function namedPathShareOptions(userNamedPaths) {
|
|
198
|
+
return userNamedPaths ? { failOnIgnoredNamedPaths: true } : {};
|
|
199
|
+
}
|
|
168
200
|
/**
|
|
169
201
|
* Optional detail line listing the effective write prefixes when a push
|
|
170
202
|
* dropped files — helps debug ACL vs scope-excluded mismatches (feedback_7e9c535e).
|
|
@@ -561,6 +593,12 @@ export async function pushAll(options, deps) {
|
|
|
561
593
|
result.filesRefusedStalePaths ??= [];
|
|
562
594
|
result.filesRefusedStalePaths.push(...(r.filesRefusedStalePaths ?? []));
|
|
563
595
|
}
|
|
596
|
+
if ((r.filesUploadFailed ?? 0) > 0) {
|
|
597
|
+
result.filesUploadFailed =
|
|
598
|
+
(result.filesUploadFailed ?? 0) + (r.filesUploadFailed ?? 0);
|
|
599
|
+
result.uploadFailedPaths ??= [];
|
|
600
|
+
result.uploadFailedPaths.push(...(r.uploadFailedPaths ?? []));
|
|
601
|
+
}
|
|
564
602
|
result.perCompany.push({
|
|
565
603
|
slug: entry.slug,
|
|
566
604
|
result: r,
|
|
@@ -947,6 +985,9 @@ export function registerCloudCommands(program) {
|
|
|
947
985
|
// Otherwise preserve the historical "default to cwd" semantics so
|
|
948
986
|
// `hq sync push <file>` and bare `hq sync push` from inside the
|
|
949
987
|
// company tree both work as before.
|
|
988
|
+
// Paths the user typed fail loudly when an ignore rule drops them
|
|
989
|
+
// (feedback_63bfc302); enumerated defaults keep the quiet skip.
|
|
990
|
+
const userNamedPaths = explicitPersonalPaths !== undefined || (paths?.length ?? 0) > 0;
|
|
950
991
|
const targetPaths = explicitPersonalPaths
|
|
951
992
|
? explicitPersonalPaths
|
|
952
993
|
: paths && paths.length > 0
|
|
@@ -1019,9 +1060,11 @@ export function registerCloudCommands(program) {
|
|
|
1019
1060
|
...(journalSlug !== undefined ? { journalSlug } : {}),
|
|
1020
1061
|
...(author ? { author } : {}),
|
|
1021
1062
|
...(pushPrefixSet !== undefined ? { prefixSet: pushPrefixSet } : {}),
|
|
1063
|
+
...namedPathShareOptions(userNamedPaths),
|
|
1022
1064
|
});
|
|
1023
1065
|
const planLimitRefused = planLimitTracker.summary();
|
|
1024
1066
|
const staleRefusalCount = result.filesRefusedStale ?? 0;
|
|
1067
|
+
const uploadFailed = readUploadFailures(result);
|
|
1025
1068
|
const refusalRecovery = [
|
|
1026
1069
|
result.filesExcludedByScope > 0
|
|
1027
1070
|
? "Check write access for the refused path; ask its creator or a company admin to grant access."
|
|
@@ -1029,6 +1072,9 @@ export function registerCloudCommands(program) {
|
|
|
1029
1072
|
staleRefusalCount > 0
|
|
1030
1073
|
? "Pull the current vault copy, merge your edit, and push again."
|
|
1031
1074
|
: null,
|
|
1075
|
+
uploadFailed.count > 0
|
|
1076
|
+
? "Push again; the files that failed are still on this machine."
|
|
1077
|
+
: null,
|
|
1032
1078
|
]
|
|
1033
1079
|
.filter((message) => message !== null)
|
|
1034
1080
|
.join(" ");
|
|
@@ -1049,6 +1095,8 @@ export function registerCloudCommands(program) {
|
|
|
1049
1095
|
filesExcludedByScope: result.filesExcludedByScope,
|
|
1050
1096
|
filesRefusedStale: staleRefusalCount,
|
|
1051
1097
|
filesRefusedStalePaths: result.filesRefusedStalePaths ?? [],
|
|
1098
|
+
filesUploadFailed: uploadFailed.count,
|
|
1099
|
+
uploadFailedPaths: uploadFailed.paths,
|
|
1052
1100
|
...(refusalRecovery ? { refusalRecovery } : {}),
|
|
1053
1101
|
...(planLimitRefused ? { planLimitRefused } : {}),
|
|
1054
1102
|
});
|
|
@@ -1081,26 +1129,33 @@ export function registerCloudCommands(program) {
|
|
|
1081
1129
|
for (const line of staleUploadRefusalLines(result.filesRefusedStale ?? 0, result.filesRefusedStalePaths)) {
|
|
1082
1130
|
log(chalk.yellow(line));
|
|
1083
1131
|
}
|
|
1132
|
+
for (const line of uploadFailedLines(uploadFailed.count, uploadFailed.paths)) {
|
|
1133
|
+
log(chalk.yellow(line));
|
|
1134
|
+
}
|
|
1084
1135
|
await syncHealth.succeeded();
|
|
1085
1136
|
process.exit(1);
|
|
1086
1137
|
return;
|
|
1087
1138
|
}
|
|
1088
|
-
|
|
1139
|
+
const notUploadedCount = result.filesExcludedByScope + staleRefusalCount + uploadFailed.count;
|
|
1140
|
+
if (notUploadedCount > 0) {
|
|
1089
1141
|
log(chalk.yellow(`\n⚠ Pushed ${result.filesUploaded} file(s) (${formatBytes(result.bytesUploaded)}, ` +
|
|
1090
|
-
`${result.filesSkipped} skipped, ${
|
|
1142
|
+
`${result.filesSkipped} skipped, ${notUploadedCount} files not uploaded)`));
|
|
1091
1143
|
for (const line of scopeExcludedLines(result.filesExcludedByScope, pushPrefixSet)) {
|
|
1092
1144
|
log(chalk.yellow(line));
|
|
1093
1145
|
}
|
|
1094
1146
|
for (const line of staleUploadRefusalLines(staleRefusalCount, result.filesRefusedStalePaths)) {
|
|
1095
1147
|
log(chalk.yellow(line));
|
|
1096
1148
|
}
|
|
1149
|
+
for (const line of uploadFailedLines(uploadFailed.count, uploadFailed.paths)) {
|
|
1150
|
+
log(chalk.yellow(line));
|
|
1151
|
+
}
|
|
1097
1152
|
}
|
|
1098
1153
|
else {
|
|
1099
1154
|
const skipSummary = formatPushSkipSummary(result);
|
|
1100
1155
|
log(chalk.green(`\n✓ Pushed ${result.filesUploaded} file(s) (${formatBytes(result.bytesUploaded)}, ${skipSummary})`));
|
|
1101
1156
|
}
|
|
1102
1157
|
await syncHealth.succeeded();
|
|
1103
|
-
const exitCode = scopeExcludedExitCode(
|
|
1158
|
+
const exitCode = scopeExcludedExitCode(notUploadedCount);
|
|
1104
1159
|
if (exitCode !== 0) {
|
|
1105
1160
|
process.exit(exitCode);
|
|
1106
1161
|
return;
|
|
@@ -1278,6 +1333,7 @@ export function registerCloudCommands(program) {
|
|
|
1278
1333
|
.command("status")
|
|
1279
1334
|
.description("Show local sync journal summary")
|
|
1280
1335
|
.option("--hq-root <path>", `Local HQ tree root (default: ${DEFAULT_HQ_ROOT})`, DEFAULT_HQ_ROOT)
|
|
1336
|
+
.option("--company <slug>", "Show only this company's journal (company slug)")
|
|
1281
1337
|
.action((options, command) => {
|
|
1282
1338
|
const explicitHqRoot = command.getOptionValueSource("hqRoot") === "cli";
|
|
1283
1339
|
try {
|
|
@@ -1312,7 +1368,17 @@ export function registerCloudCommands(program) {
|
|
|
1312
1368
|
// Journals live in the machine-wide state directory, independently
|
|
1313
1369
|
// of the HQ content root selected for this status display.
|
|
1314
1370
|
const stateDir = getStateDir();
|
|
1315
|
-
const
|
|
1371
|
+
const companyFilter = options.company?.trim();
|
|
1372
|
+
if (options.company !== undefined && !companyFilter) {
|
|
1373
|
+
throw new Error("--company needs a company slug");
|
|
1374
|
+
}
|
|
1375
|
+
// Journals are keyed by slug; a company UID would silently match none.
|
|
1376
|
+
if (companyFilter?.startsWith("cmp_")) {
|
|
1377
|
+
throw new Error(`--company takes the company slug, not its UID (${companyFilter})`);
|
|
1378
|
+
}
|
|
1379
|
+
const journals = companyFilter
|
|
1380
|
+
? listJournals().filter((j) => j.slug === companyFilter)
|
|
1381
|
+
: listJournals();
|
|
1316
1382
|
let grandFiles = 0;
|
|
1317
1383
|
let grandBytes = 0;
|
|
1318
1384
|
let grandLastSync = "";
|
|
@@ -1320,7 +1386,13 @@ export function registerCloudCommands(program) {
|
|
|
1320
1386
|
console.log(` HQ root: ${hqRoot}`);
|
|
1321
1387
|
console.log(` Active company: ${activeCompany ?? chalk.dim("(none)")}`);
|
|
1322
1388
|
console.log(` State dir: ${stateDir}`);
|
|
1389
|
+
if (companyFilter)
|
|
1390
|
+
console.log(` Company: ${companyFilter}`);
|
|
1323
1391
|
console.log(` Journals: ${journals.length}`);
|
|
1392
|
+
if (journals.length === 0 && companyFilter) {
|
|
1393
|
+
console.log(chalk.dim(`No sync journal for company "${companyFilter}" yet — run \`hq sync now --company ${companyFilter}\` to create one.`));
|
|
1394
|
+
return;
|
|
1395
|
+
}
|
|
1324
1396
|
if (journals.length === 0) {
|
|
1325
1397
|
console.log(chalk.dim("No sync journal yet — run `hq sync now`, `hq sync push`, or `hq sync pull` to create one."));
|
|
1326
1398
|
return;
|
|
@@ -1355,6 +1427,7 @@ export function registerCloudCommands(program) {
|
|
|
1355
1427
|
console.log(` Last sync: ${grandLastSync || "never"}`);
|
|
1356
1428
|
}
|
|
1357
1429
|
printReadOnlyLocalOnly(hqRoot, journals);
|
|
1430
|
+
printServerOwnedLocalOnly(hqRoot, journals);
|
|
1358
1431
|
}
|
|
1359
1432
|
catch (err) {
|
|
1360
1433
|
console.error(chalk.red("✗ Status failed:"), err instanceof Error ? err.message : String(err));
|
|
@@ -1724,13 +1797,18 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal, skipCompani
|
|
|
1724
1797
|
else if (row.result) {
|
|
1725
1798
|
const r = row.result;
|
|
1726
1799
|
const legRefused = r.planLimit ?? null;
|
|
1727
|
-
const status = r.aborted ||
|
|
1800
|
+
const status = r.aborted ||
|
|
1801
|
+
r.filesExcludedByScope > 0 ||
|
|
1802
|
+
(r.filesRefusedStale ?? 0) > 0 ||
|
|
1803
|
+
(r.filesUploadFailed ?? 0) > 0 ||
|
|
1804
|
+
legRefused
|
|
1728
1805
|
? chalk.yellow("⚠")
|
|
1729
1806
|
: chalk.green("✓");
|
|
1730
1807
|
console.log(` ${status} ${row.slug}: ${r.filesUploaded} file(s), ` +
|
|
1731
1808
|
`${formatBytes(r.bytesUploaded)}, ${r.filesSkipped} skipped, ` +
|
|
1732
1809
|
`${r.filesDeleted} deleted, ${r.filesExcludedByScope} scope-excluded, ` +
|
|
1733
|
-
`${r.filesRefusedStale ?? 0} stale-refused, ${r.
|
|
1810
|
+
`${r.filesRefusedStale ?? 0} stale-refused, ${r.filesUploadFailed ?? 0} failed, ` +
|
|
1811
|
+
`${r.conflictPaths.length} conflict(s)` +
|
|
1734
1812
|
(r.aborted ? " — aborted" : "") +
|
|
1735
1813
|
(legRefused
|
|
1736
1814
|
? ` — ${describeRefusedCount(legRefused)} refused by plan limit`
|
|
@@ -1747,11 +1825,16 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal, skipCompani
|
|
|
1747
1825
|
`(${formatBytes(result.bytesUploaded)}) across ${result.attempted} ` +
|
|
1748
1826
|
`target(s); ${result.filesDeleted} deleted; ${errored} error(s); ` +
|
|
1749
1827
|
`${result.filesExcludedByScope} scope-excluded; ` +
|
|
1750
|
-
`${result.filesRefusedStale ?? 0} stale-refused` +
|
|
1828
|
+
`${result.filesRefusedStale ?? 0} stale-refused; ` +
|
|
1829
|
+
`${result.filesUploadFailed ?? 0} failed` +
|
|
1751
1830
|
(refusedTargets > 0
|
|
1752
1831
|
? `; new files refused by a plan limit on ${refusedTargets} target(s)`
|
|
1753
1832
|
: "");
|
|
1754
|
-
console.log(errored > 0 ||
|
|
1833
|
+
console.log(errored > 0 ||
|
|
1834
|
+
result.filesExcludedByScope > 0 ||
|
|
1835
|
+
(result.filesRefusedStale ?? 0) > 0 ||
|
|
1836
|
+
(result.filesUploadFailed ?? 0) > 0 ||
|
|
1837
|
+
refusedTargets > 0
|
|
1755
1838
|
? chalk.yellow(summary)
|
|
1756
1839
|
: chalk.green(summary));
|
|
1757
1840
|
for (const row of result.perCompany) {
|
|
@@ -1772,6 +1855,9 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal, skipCompani
|
|
|
1772
1855
|
for (const line of staleUploadRefusalLines(staleCount, row.result?.filesRefusedStalePaths)) {
|
|
1773
1856
|
console.log(chalk.yellow(`${row.slug}: ${line}`));
|
|
1774
1857
|
}
|
|
1858
|
+
for (const line of uploadFailedLines(row.result?.filesUploadFailed ?? 0, row.result?.uploadFailedPaths)) {
|
|
1859
|
+
console.log(chalk.yellow(`${row.slug}: ${line}`));
|
|
1860
|
+
}
|
|
1775
1861
|
}
|
|
1776
1862
|
if (errored > 0)
|
|
1777
1863
|
throw new SyncExitError(1);
|
|
@@ -1780,10 +1866,12 @@ async function runPushAll(hqRoot, message, onConflict, skipPersonal, skipCompani
|
|
|
1780
1866
|
filesExcludedByScope: result.filesExcludedByScope,
|
|
1781
1867
|
filesRefusedStale: result.filesRefusedStale ?? 0,
|
|
1782
1868
|
filesRefusedStalePaths: [...new Set(result.filesRefusedStalePaths ?? [])],
|
|
1869
|
+
filesUploadFailed: result.filesUploadFailed ?? 0,
|
|
1783
1870
|
};
|
|
1784
1871
|
return (refusalSummary.planLimitRefused ||
|
|
1785
1872
|
refusalSummary.filesExcludedByScope > 0 ||
|
|
1786
|
-
refusalSummary.filesRefusedStale > 0
|
|
1873
|
+
refusalSummary.filesRefusedStale > 0 ||
|
|
1874
|
+
refusalSummary.filesUploadFailed > 0
|
|
1787
1875
|
? refusalSummary
|
|
1788
1876
|
: null);
|
|
1789
1877
|
}
|
|
@@ -1872,9 +1960,11 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
1872
1960
|
...(nowPushPrefixSet !== undefined ? { prefixSet: nowPushPrefixSet } : {}),
|
|
1873
1961
|
});
|
|
1874
1962
|
const pushRefused = planLimitTracker.summary();
|
|
1963
|
+
const pushUploadFailed = readUploadFailures(pushResult);
|
|
1875
1964
|
const pushStatus = pushResult.aborted ||
|
|
1876
1965
|
pushResult.filesExcludedByScope > 0 ||
|
|
1877
1966
|
(pushResult.filesRefusedStale ?? 0) > 0 ||
|
|
1967
|
+
pushUploadFailed.count > 0 ||
|
|
1878
1968
|
pushRefused
|
|
1879
1969
|
? chalk.yellow("⚠")
|
|
1880
1970
|
: chalk.green("✓");
|
|
@@ -1895,6 +1985,9 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
1895
1985
|
for (const line of staleUploadRefusalLines(pushResult.filesRefusedStale ?? 0, pushResult.filesRefusedStalePaths)) {
|
|
1896
1986
|
console.log(chalk.yellow(line));
|
|
1897
1987
|
}
|
|
1988
|
+
for (const line of uploadFailedLines(pushUploadFailed.count, pushUploadFailed.paths)) {
|
|
1989
|
+
console.log(chalk.yellow(line));
|
|
1990
|
+
}
|
|
1898
1991
|
if (pushResult.aborted) {
|
|
1899
1992
|
console.log(chalk.yellow("\n⚠ Sync now aborted on push leg; pull skipped."));
|
|
1900
1993
|
throw new SyncExitError(1);
|
|
@@ -2016,7 +2109,8 @@ async function runNowSingle(hqRoot, company, personal, message, onConflict, mode
|
|
|
2016
2109
|
throw new SyncExitError(1, { health: "succeeded" });
|
|
2017
2110
|
}
|
|
2018
2111
|
if (pushResult.filesExcludedByScope > 0 ||
|
|
2019
|
-
(pushResult.filesRefusedStale ?? 0) > 0
|
|
2112
|
+
(pushResult.filesRefusedStale ?? 0) > 0 ||
|
|
2113
|
+
pushUploadFailed.count > 0) {
|
|
2020
2114
|
console.log(chalk.yellow("\n⚠ Sync now finished, but one or more requested uploads were refused."));
|
|
2021
2115
|
throw new SyncExitError(1, { health: "succeeded" });
|
|
2022
2116
|
}
|
|
@@ -2141,6 +2235,52 @@ export function printReadOnlyLocalOnly(hqRoot, journals, log = (line) => console
|
|
|
2141
2235
|
}
|
|
2142
2236
|
return grand;
|
|
2143
2237
|
}
|
|
2238
|
+
/**
|
|
2239
|
+
* `hq sync status`: list local files under server-owned folders (sources/,
|
|
2240
|
+
* signals/, ontology/ outside entities/) that are not in the sync journal.
|
|
2241
|
+
* The server pipeline writes these folders; a client push refuses them and
|
|
2242
|
+
* the recurring runner skips them quietly, so a file written locally there
|
|
2243
|
+
* never reaches the vault and `pendingFiles: 0` still reports a clean tree
|
|
2244
|
+
* (feedback_0414d71c). Uses only the journals already loaded, with no
|
|
2245
|
+
* network calls. Prints nothing when nothing applies.
|
|
2246
|
+
*/
|
|
2247
|
+
export function printServerOwnedLocalOnly(hqRoot, journals, log = (line) => console.log(line)) {
|
|
2248
|
+
let shouldSync;
|
|
2249
|
+
let grand = 0;
|
|
2250
|
+
for (const { slug, journal } of journals) {
|
|
2251
|
+
if (slug === PERSONAL_VAULT_JOURNAL_SLUG)
|
|
2252
|
+
continue;
|
|
2253
|
+
if (!fs.existsSync(path.join(hqRoot, "companies", slug)))
|
|
2254
|
+
continue;
|
|
2255
|
+
if (!shouldSync) {
|
|
2256
|
+
try {
|
|
2257
|
+
shouldSync = createIgnoreFilter(hqRoot);
|
|
2258
|
+
}
|
|
2259
|
+
catch {
|
|
2260
|
+
shouldSync = () => true;
|
|
2261
|
+
}
|
|
2262
|
+
}
|
|
2263
|
+
const journalKeys = new Set(Object.keys(journal.files ?? {}));
|
|
2264
|
+
const found = findServerOwnedLocalOnly({ hqRoot, slug, journalKeys, shouldSync });
|
|
2265
|
+
if (found.total === 0)
|
|
2266
|
+
continue;
|
|
2267
|
+
grand += found.total;
|
|
2268
|
+
log(chalk.yellow(`\n ! ${slug}: ${found.total} local-only file(s) under server-owned folders ` +
|
|
2269
|
+
`(${SERVER_OWNED_FOLDERS.map((f) => `${f}/`).join(", ")}) never upload`));
|
|
2270
|
+
for (const rel of found.files) {
|
|
2271
|
+
log(` ${rel}`);
|
|
2272
|
+
}
|
|
2273
|
+
if (found.total > found.files.length) {
|
|
2274
|
+
log(chalk.dim(` ... and ${found.total - found.files.length} more`));
|
|
2275
|
+
}
|
|
2276
|
+
}
|
|
2277
|
+
if (grand > 0) {
|
|
2278
|
+
log(chalk.dim(" The cloud pipeline writes these folders; push skips them and does not count them as pending. " +
|
|
2279
|
+
"Keep notes you write yourself under knowledge/ (which syncs), or add sources through an " +
|
|
2280
|
+
"integration or ingestion flow and run hq sync pull."));
|
|
2281
|
+
}
|
|
2282
|
+
return grand;
|
|
2283
|
+
}
|
|
2144
2284
|
function scopeLabel(slug) {
|
|
2145
2285
|
if (slug === PERSONAL_VAULT_JOURNAL_SLUG)
|
|
2146
2286
|
return "personal vault";
|
package/dist/commands/company.js
CHANGED
|
@@ -13,7 +13,8 @@ function workTrackingFromBody(body) {
|
|
|
13
13
|
}
|
|
14
14
|
/**
|
|
15
15
|
* Read both switches from GET /company-settings (owner or admin only). The
|
|
16
|
-
* server returns effective values, so an absent field means the default
|
|
16
|
+
* server returns effective values, so an absent field means the default
|
|
17
|
+
* (registry off, live on).
|
|
17
18
|
* A refused or failed read returns null rather than a guess.
|
|
18
19
|
*/
|
|
19
20
|
async function readWorkTrackingSettings(token, companyUid) {
|
|
@@ -28,7 +29,7 @@ async function readWorkTrackingSettings(token, companyUid) {
|
|
|
28
29
|
const body = (await res.json().catch(() => ({})));
|
|
29
30
|
return {
|
|
30
31
|
workRegistryEnabled: body.workRegistryEnabled === true,
|
|
31
|
-
workMeshLiveEnabled: body.workMeshLiveEnabled
|
|
32
|
+
workMeshLiveEnabled: body.workMeshLiveEnabled !== false,
|
|
32
33
|
};
|
|
33
34
|
}
|
|
34
35
|
catch {
|
|
@@ -97,7 +98,7 @@ export function registerCompanyCommand(program) {
|
|
|
97
98
|
.option("--accounting-enabled <true|false>", "Enable/disable the accounting pack for this company")
|
|
98
99
|
.option("--checkpoint-gate-enabled <true|false>", "Require (true) or stop requiring (false) a checkpoint before HQ sessions in this company end a turn")
|
|
99
100
|
.option("--work-registry-enabled <true|false>", "Turn work-session registration on or off for this company (default off). Work tracking needs this and --work-mesh-live-enabled")
|
|
100
|
-
.option("--work-mesh-live-enabled <true|false>", "Turn live work tracking on or off for this company (default
|
|
101
|
+
.option("--work-mesh-live-enabled <true|false>", "Turn live work tracking on or off for this company (default on; set false to opt out). Work tracking needs this and --work-registry-enabled")
|
|
101
102
|
.action(async (opts) => {
|
|
102
103
|
try {
|
|
103
104
|
const companySlug = company.opts().company;
|
|
@@ -27,6 +27,16 @@ export declare const NO_BACKEND_RETRY_MS: number;
|
|
|
27
27
|
* always releases the queue before the lock would be presumed abandoned.
|
|
28
28
|
*/
|
|
29
29
|
export declare const SIBLING_MAX_RUNTIME_MS: number;
|
|
30
|
+
/**
|
|
31
|
+
* Minimum gap between two maintenance siblings started by the Stop gate's
|
|
32
|
+
* automatic checkpoint (`--trigger stop-gate-auto`) for the same session. That
|
|
33
|
+
* trigger fires at the end of every turn that did not checkpoint, with no files,
|
|
34
|
+
* learnings, or decisions, so a fast-moving session started a full agent run
|
|
35
|
+
* per turn and most runs found nothing new (feedback_90c33997). Inside the
|
|
36
|
+
* window the thread file and stamps are still written; only the sibling is
|
|
37
|
+
* skipped. Explicit checkpoints are never debounced.
|
|
38
|
+
*/
|
|
39
|
+
export declare const AUTO_SIBLING_MIN_INTERVAL_MS: number;
|
|
30
40
|
/**
|
|
31
41
|
* Queued payloads one sibling run may process, across its launch claim and its
|
|
32
42
|
* own queue drain. Each one runs the full upgrade flow, so a 50-deep queue in
|
|
@@ -80,6 +90,14 @@ export type WriteStampsOptions = {
|
|
|
80
90
|
waitForRetry?: (delayMs: number) => void;
|
|
81
91
|
};
|
|
82
92
|
export declare function writeStamps(liveRoot: string, sessionId: string | undefined, options?: WriteStampsOptions): string[];
|
|
93
|
+
/**
|
|
94
|
+
* True when an automatic checkpoint for this session already started (or
|
|
95
|
+
* queued) a sibling within AUTO_SIBLING_MIN_INTERVAL_MS. Without a session ID
|
|
96
|
+
* there is no per-session key, so the run is never debounced. A missing or
|
|
97
|
+
* unreadable stamp also counts as "not recent": a wrong answer costs one extra
|
|
98
|
+
* sibling, never a lost checkpoint.
|
|
99
|
+
*/
|
|
100
|
+
export declare function autoSiblingRecent(liveRoot: string, trigger: string | undefined, sessionId: string | undefined, nowMs: number): boolean;
|
|
83
101
|
/**
|
|
84
102
|
* Backends whose most recent run died without producing a report.
|
|
85
103
|
*
|
|
@@ -90,6 +90,17 @@ const SIBLING_LOCK_TTL_MS = 60 * 60 * 1000;
|
|
|
90
90
|
* always releases the queue before the lock would be presumed abandoned.
|
|
91
91
|
*/
|
|
92
92
|
export const SIBLING_MAX_RUNTIME_MS = 30 * 60 * 1000;
|
|
93
|
+
/**
|
|
94
|
+
* Minimum gap between two maintenance siblings started by the Stop gate's
|
|
95
|
+
* automatic checkpoint (`--trigger stop-gate-auto`) for the same session. That
|
|
96
|
+
* trigger fires at the end of every turn that did not checkpoint, with no files,
|
|
97
|
+
* learnings, or decisions, so a fast-moving session started a full agent run
|
|
98
|
+
* per turn and most runs found nothing new (feedback_90c33997). Inside the
|
|
99
|
+
* window the thread file and stamps are still written; only the sibling is
|
|
100
|
+
* skipped. Explicit checkpoints are never debounced.
|
|
101
|
+
*/
|
|
102
|
+
export const AUTO_SIBLING_MIN_INTERVAL_MS = 30 * 60 * 1000;
|
|
103
|
+
const AUTO_TRIGGER = "stop-gate-auto";
|
|
93
104
|
/** Newest-wins cap so a wedged sibling cannot grow an unbounded queue. */
|
|
94
105
|
const MAX_PENDING_PAYLOADS = 50;
|
|
95
106
|
/**
|
|
@@ -243,6 +254,8 @@ report instead of guessing.
|
|
|
243
254
|
never overwrite) so the next sibling takes it, and record that in the report.
|
|
244
255
|
NEVER read the queue and truncate it in place: a payload appended between
|
|
245
256
|
your read and the truncate is lost, and its checkpoint is never enriched.
|
|
257
|
+
A missing or empty file means the queue is empty; skip this step. Do not
|
|
258
|
+
treat it as an error.
|
|
246
259
|
`;
|
|
247
260
|
/**
|
|
248
261
|
* Rendered into the sibling prompt only when the payload names a company. The
|
|
@@ -573,6 +586,42 @@ export function writeStamps(liveRoot, sessionId, options = {}) {
|
|
|
573
586
|
}
|
|
574
587
|
return stampPaths;
|
|
575
588
|
}
|
|
589
|
+
function autoSiblingStampPath(liveRoot, sessionId) {
|
|
590
|
+
const sessionKey = sessionId.replace(/[^A-Za-z0-9._-]/g, "_") || "unknown";
|
|
591
|
+
return path.join(liveRoot, "workspace", "orchestrator", "hook-state", `checkpoint-auto-sibling-${sessionKey}`);
|
|
592
|
+
}
|
|
593
|
+
/**
|
|
594
|
+
* True when an automatic checkpoint for this session already started (or
|
|
595
|
+
* queued) a sibling within AUTO_SIBLING_MIN_INTERVAL_MS. Without a session ID
|
|
596
|
+
* there is no per-session key, so the run is never debounced. A missing or
|
|
597
|
+
* unreadable stamp also counts as "not recent": a wrong answer costs one extra
|
|
598
|
+
* sibling, never a lost checkpoint.
|
|
599
|
+
*/
|
|
600
|
+
export function autoSiblingRecent(liveRoot, trigger, sessionId, nowMs) {
|
|
601
|
+
if (trigger !== AUTO_TRIGGER || !sessionId?.trim())
|
|
602
|
+
return false;
|
|
603
|
+
try {
|
|
604
|
+
const last = Number(fs.readFileSync(autoSiblingStampPath(liveRoot, sessionId), "utf8").trim());
|
|
605
|
+
if (!Number.isFinite(last))
|
|
606
|
+
return false;
|
|
607
|
+
const age = nowMs - last;
|
|
608
|
+
return age >= 0 && age < AUTO_SIBLING_MIN_INTERVAL_MS;
|
|
609
|
+
}
|
|
610
|
+
catch {
|
|
611
|
+
return false;
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
function stampAutoSibling(liveRoot, trigger, sessionId, nowMs) {
|
|
615
|
+
if (trigger !== AUTO_TRIGGER || !sessionId?.trim())
|
|
616
|
+
return;
|
|
617
|
+
try {
|
|
618
|
+
fs.writeFileSync(autoSiblingStampPath(liveRoot, sessionId), String(nowMs));
|
|
619
|
+
}
|
|
620
|
+
catch {
|
|
621
|
+
// The debounce stamp only saves cost; failing to write it must not fail
|
|
622
|
+
// the checkpoint.
|
|
623
|
+
}
|
|
624
|
+
}
|
|
576
625
|
/**
|
|
577
626
|
* Codex Stop-gate rollout scope: every HQ user on the operator domain, rather
|
|
578
627
|
* than the single account that dogfooded it first. Eligibility is decided on
|
|
@@ -1343,6 +1392,15 @@ async function startSibling(liveRoot, input, threadPath, backend) {
|
|
|
1343
1392
|
fs.mkdirSync(runDir, { recursive: true });
|
|
1344
1393
|
const claimPath = path.join(runDir, "pending-claimed.jsonl");
|
|
1345
1394
|
payload.pending_payloads = limitClaimedPayloads(claimPending(pendingPath, claimPath), claimPath, pendingPath);
|
|
1395
|
+
// The sibling prompt reads and renames this file. Leave an empty queue behind
|
|
1396
|
+
// when the claim moved it away (or none existed), so the sibling never trips
|
|
1397
|
+
// on a missing file. Append mode: a payload queued in the meantime is kept.
|
|
1398
|
+
try {
|
|
1399
|
+
fs.closeSync(fs.openSync(pendingPath, "a"));
|
|
1400
|
+
}
|
|
1401
|
+
catch {
|
|
1402
|
+
// Best effort: the prompt also treats a missing queue as empty.
|
|
1403
|
+
}
|
|
1346
1404
|
if (!payload.company) {
|
|
1347
1405
|
let queued;
|
|
1348
1406
|
try {
|
|
@@ -1455,9 +1513,12 @@ async function runCheckpoint(options, command, group, preResolvedHqRoot) {
|
|
|
1455
1513
|
// Resolved before resolveBackend so an opted-out run pays for no backend
|
|
1456
1514
|
// probe: `--backend auto` shells out to every installed CLI for a version.
|
|
1457
1515
|
const siblingOptOut = options.agent === false || !siblingEnabledBySetting(liveRoot);
|
|
1516
|
+
const autoDebounced = !siblingOptOut && autoSiblingRecent(liveRoot, input.trigger, input.sessionId, Date.now());
|
|
1458
1517
|
// Validated even when nothing will spawn, so `--backend bogus` is still an error.
|
|
1459
1518
|
const requestedBackend = assertKnownBackend(options.backend);
|
|
1460
|
-
const backend = siblingOptOut
|
|
1519
|
+
const backend = siblingOptOut || autoDebounced
|
|
1520
|
+
? "none"
|
|
1521
|
+
: await resolveBackend(requestedBackend, liveRoot, input.company, { dryRun: options.dryRun });
|
|
1461
1522
|
const now = new Date();
|
|
1462
1523
|
const threadId = `T-${formatTimestamp(now)}-auto-${summarySlug(input.summary)}`;
|
|
1463
1524
|
const threadPath = path.join(liveRoot, "workspace", "threads", `${threadId}.json`);
|
|
@@ -1478,7 +1539,7 @@ async function runCheckpoint(options, command, group, preResolvedHqRoot) {
|
|
|
1478
1539
|
worker: input.worker,
|
|
1479
1540
|
},
|
|
1480
1541
|
stamps: stampPaths.map((stampPath) => path.relative(liveRoot, stampPath)),
|
|
1481
|
-
sibling: siblingOptOut ? null : { backend },
|
|
1542
|
+
sibling: siblingOptOut || autoDebounced ? null : { backend },
|
|
1482
1543
|
});
|
|
1483
1544
|
return;
|
|
1484
1545
|
}
|
|
@@ -1517,11 +1578,15 @@ async function runCheckpoint(options, command, group, preResolvedHqRoot) {
|
|
|
1517
1578
|
if (siblingOptOut) {
|
|
1518
1579
|
printResult(`checkpoint: sibling disabled (${SIBLING_SETTING})`);
|
|
1519
1580
|
}
|
|
1581
|
+
else if (autoDebounced) {
|
|
1582
|
+
printResult("checkpoint: sibling skipped (automatic checkpoint; one ran for this session recently)");
|
|
1583
|
+
}
|
|
1520
1584
|
else if (backend === "none") {
|
|
1521
1585
|
printResult("checkpoint: sibling disabled (backend none)");
|
|
1522
1586
|
}
|
|
1523
1587
|
else {
|
|
1524
1588
|
await startSibling(liveRoot, input, threadPath, backend);
|
|
1589
|
+
stampAutoSibling(liveRoot, input.trigger, input.sessionId, Date.now());
|
|
1525
1590
|
}
|
|
1526
1591
|
}
|
|
1527
1592
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { spawnSync } from "node:child_process";
|
|
2
|
-
export declare function
|
|
2
|
+
export declare function queueMaxSeconds(env?: NodeJS.ProcessEnv): number;
|
|
3
|
+
export declare function scanDriverState(stateDir: string, maxFails: number, hqBin: string, run?: typeof spawnSync, queueMax?: number): string;
|
|
3
4
|
export declare function checkDriverStall(stateDir: string, stallWindow: number, hqBin: string, run?: typeof spawnSync): string;
|
|
4
5
|
export declare function acquirePipelineDriverLock(driverDir: string, pid: number): {
|
|
5
6
|
acquired: boolean;
|
|
@@ -14,6 +15,16 @@ export declare function stalledLaneContext(state: string, worker: string): {
|
|
|
14
15
|
};
|
|
15
16
|
export declare function pendingLaneQuestion(text: string, lane: string): string;
|
|
16
17
|
export declare function sleep(ms: number, signal?: AbortSignal): Promise<void>;
|
|
18
|
+
/**
|
|
19
|
+
* Move a handoff that failed validation to a numbered sibling
|
|
20
|
+
* (handoffs/<story>-<phase>.invalid.<n>.json) so the worker's evidence is never
|
|
21
|
+
* lost, and write a failed handoff in its place that names the validation
|
|
22
|
+
* error and the kept file. Returns both paths.
|
|
23
|
+
*/
|
|
24
|
+
export declare function quarantineInvalidHandoff(state: string, storyId: string, phase: string, handoffPath: string, reason: string): {
|
|
25
|
+
keptPath: string;
|
|
26
|
+
handoffPath: string;
|
|
27
|
+
};
|
|
17
28
|
export declare function stopDriverLanes(state: string, hq: string, logFile: string, options?: {
|
|
18
29
|
timeoutMs?: number;
|
|
19
30
|
pollMs?: number;
|