@indigoai-us/hq-cli 5.311.4 → 5.312.1
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 +20 -0
- package/assets/scaffold/core/scripts/lib/work-mesh-live-hook.sh +378 -0
- package/assets/scaffold/core/scripts/lib/work-mesh-live-rebind.sh +141 -0
- package/assets/scaffold/core/scripts/migrate-policy-triggers.sh +373 -0
- package/assets/scaffold/core/scripts/register-project.sh +262 -0
- package/assets/scaffold/core/scripts/work-mesh-live-rebind.sh +59 -0
- package/dist/command-catalog.generated.d.ts +160 -0
- package/dist/command-catalog.generated.js +200 -0
- package/dist/command-registration-plan.d.ts +6 -0
- package/dist/command-registration-plan.js +1 -0
- package/dist/commands/company-rename-flag.d.ts +4 -0
- package/dist/commands/company-rename-flag.js +7 -0
- package/dist/commands/company-rename.d.ts +4 -0
- package/dist/commands/company-rename.js +90 -0
- package/dist/commands/company.js +2 -0
- package/dist/commands/core-checkpoint-stop-gate-native.js +12 -0
- package/dist/commands/core-eval-trigger-native.d.ts +4 -0
- package/dist/commands/core-eval-trigger-native.js +12 -0
- package/dist/commands/core-migrate-policy-triggers-native.d.ts +21 -0
- package/dist/commands/core-migrate-policy-triggers-native.js +378 -0
- package/dist/commands/core-native-hook-helper-commands.d.ts +4 -0
- package/dist/commands/core-native-hook-helper-commands.js +56 -0
- package/dist/commands/core-native-hook-helpers-flag.d.ts +4 -0
- package/dist/commands/core-native-hook-helpers-flag.js +7 -0
- package/dist/commands/core-register-project-native.d.ts +30 -0
- package/dist/commands/core-register-project-native.js +347 -0
- package/dist/commands/core-work-mesh-live-rebind-native.d.ts +18 -0
- package/dist/commands/core-work-mesh-live-rebind-native.js +262 -0
- package/dist/commands/core.js +7 -0
- package/dist/commands/daemon.js +22 -0
- package/dist/commands/dm.js +22 -6
- package/dist/commands/files-browse.js +16 -4
- package/dist/commands/lanes-message.js +17 -2
- package/dist/commands/lanes-shared.d.ts +1 -0
- package/dist/commands/lanes.js +96 -20
- package/dist/commands/vault-retention-flag.d.ts +3 -0
- package/dist/commands/vault-retention-flag.js +9 -0
- package/dist/commands/vault-retention.d.ts +11 -0
- package/dist/commands/vault-retention.js +69 -0
- package/dist/fast-core.js +10 -3
- package/dist/fast-scaffold.d.ts +2 -0
- package/dist/fast-scaffold.js +11 -0
- package/dist/lib/agents/model-catalog.d.ts +3 -2
- package/dist/lib/company-skill-wrapper.d.ts +0 -8
- package/dist/lib/core-utils/codex-skill-bridge.d.ts +2 -1
- package/dist/lib/core-utils/sentry-report.js +21 -4
- package/dist/lib/daemon/catalog.d.ts +2 -0
- package/dist/lib/daemon/catalog.js +13 -1
- package/dist/lib/daemon/config.d.ts +2 -0
- package/dist/lib/daemon/config.js +4 -0
- package/dist/lib/daemon/run.d.ts +2 -0
- package/dist/lib/daemon/run.js +5 -0
- package/dist/lib/deploy-api.d.ts +0 -1
- package/dist/lib/deploy-api.js +1 -1
- package/dist/lib/doctor/checks/work-context.js +9 -1
- package/dist/lib/lanes/autopilot.d.ts +38 -1
- package/dist/lib/lanes/autopilot.js +353 -5
- package/dist/lib/lanes/flag-keys.d.ts +9 -1
- package/dist/lib/lanes/flag-keys.js +9 -1
- package/dist/lib/lanes/keep-alive.d.ts +2 -0
- package/dist/lib/lanes/keep-alive.js +2 -0
- package/dist/lib/lanes/reconcile.d.ts +5 -0
- package/dist/lib/lanes/reconcile.js +57 -4
- package/dist/lib/lanes/watcher-main.js +20 -1
- package/dist/lib/lanes/watcher.d.ts +9 -0
- package/dist/lib/lanes/watcher.js +47 -1
- package/dist/lib/mesh/client.js +23 -5
- package/dist/lib/mesh/denial-codes.d.ts +5 -0
- package/dist/lib/mesh/denial-codes.js +14 -0
- package/dist/lib/mesh/live/daemon/doctor.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/doctor.js +11 -1
- package/dist/lib/report-run.d.ts +3 -2
- package/dist/lib/report-templates.d.ts +0 -1
- package/dist/lib/report-templates.js +0 -3
- package/dist/lib/scaffold-cli-floor.d.ts +0 -1
- package/dist/lib/scaffold-cli-floor.js +1 -1
- package/dist/lib/search-index/embed-lock.d.ts +2 -0
- package/dist/lib/search-index/embed-lock.js +10 -1
- package/dist/lib/work-context/contract.d.ts +3 -0
- package/dist/lib/work-context/contract.schema.json +3 -1
- package/dist/lib/work-context/outbox.d.ts +14 -5
- package/dist/lib/work-context/outbox.js +48 -5
- package/dist/lib/work-context/reconcile.js +26 -5
- package/dist/main.d.ts +20 -0
- package/dist/main.js +8 -1
- package/dist/run/discover-schemas.d.ts +2 -1
- package/dist/utils/core-eval-trigger.d.ts +4 -0
- package/dist/utils/core-eval-trigger.js +88 -0
- package/dist/utils/hq-roots.d.ts +0 -2
- package/dist/utils/hq-roots.js +1 -8
- package/dist/utils/incomplete-install-error.d.ts +7 -6
- package/dist/utils/large-file-guard.d.ts +0 -10
- package/dist/utils/large-file-guard.js +1 -1
- package/dist/utils/package-root-diagnostics.d.ts +1 -1
- package/dist/utils/self-update.d.ts +2 -1
- package/package.json +1 -1
package/dist/commands/daemon.js
CHANGED
|
@@ -474,6 +474,18 @@ async function runDaemonSyncNow(d, opts) {
|
|
|
474
474
|
const result = { accepted: true, action: "restart-sync-runner", daemonRunning: running };
|
|
475
475
|
d.out(opts.json ? JSON.stringify(result, null, 2) : running ? "Asked the daemon to start a fresh sync pass." : "Queued a fresh sync pass for the next daemon start.");
|
|
476
476
|
}
|
|
477
|
+
async function runDaemonSyncInstantSyncSet(d, enabled, opts) {
|
|
478
|
+
const host = currentHost(d);
|
|
479
|
+
const config = readDaemonConfig(host.paths.configPath);
|
|
480
|
+
const changed = config.instantSyncEnabled !== enabled;
|
|
481
|
+
if (changed)
|
|
482
|
+
writeDaemonConfig(host.paths.configPath, { ...config, instantSyncEnabled: enabled });
|
|
483
|
+
const daemonRunning = runningDaemonPid(host.paths.lockPath, d.daemonAlive) !== null;
|
|
484
|
+
if (changed && daemonRunning)
|
|
485
|
+
queueDaemonSyncControl(d, { action: "restart-daemon" });
|
|
486
|
+
const result = { instantSyncEnabled: enabled, persisted: true, daemonRestartRequested: changed && daemonRunning };
|
|
487
|
+
d.out(opts.json ? JSON.stringify(result, null, 2) : `Instant Sync ${enabled ? "enabled" : "disabled"}${daemonRunning ? "; asked the daemon to restart" : "; saved for the next daemon start"}.`);
|
|
488
|
+
}
|
|
477
489
|
async function runDaemonSyncPause(d, paused, opts) {
|
|
478
490
|
const host = currentHost(d);
|
|
479
491
|
const config = readDaemonConfig(host.paths.configPath);
|
|
@@ -772,6 +784,16 @@ export function registerDaemonCommand(program, over = {}) {
|
|
|
772
784
|
.description("Start a fresh sync pass immediately")
|
|
773
785
|
.option("--json", "Print machine-readable JSON")
|
|
774
786
|
.action(wrap((d, opts) => runDaemonSyncNow(d, opts)));
|
|
787
|
+
const instantSync = sync.command("instant-sync").description("Control daemon Instant Sync");
|
|
788
|
+
instantSync
|
|
789
|
+
.command("set <state>")
|
|
790
|
+
.description("Set whether daemon sync uses Instant Sync")
|
|
791
|
+
.option("--json", "Print machine-readable JSON")
|
|
792
|
+
.action(wrap((d, state, opts) => {
|
|
793
|
+
if (state !== "on" && state !== "off")
|
|
794
|
+
throw new DaemonCommandError("Use on or off for Instant Sync.");
|
|
795
|
+
return runDaemonSyncInstantSyncSet(d, state === "on", opts);
|
|
796
|
+
}));
|
|
775
797
|
sync
|
|
776
798
|
.command("pause")
|
|
777
799
|
.description("Pause daemon sync until resumed")
|
package/dist/commands/dm.js
CHANGED
|
@@ -872,7 +872,12 @@ export function formatChannelMessage(m, nowMs, opts = {}) {
|
|
|
872
872
|
const id = opts.showEventId ? (m.eventId ?? m.messageId) : undefined;
|
|
873
873
|
const idPart = id ? ` ${chalk.dim(id)}` : "";
|
|
874
874
|
const bodyIndent = `${indent} `;
|
|
875
|
-
|
|
875
|
+
const body = firstLine(m.body);
|
|
876
|
+
const readableBody = body ||
|
|
877
|
+
(senderLabel(m).toLowerCase() === "hq"
|
|
878
|
+
? "System notification (no text provided)."
|
|
879
|
+
: "(no message text)");
|
|
880
|
+
return `${indent}${who} ${when}${idPart}\n${bodyIndent}${readableBody}${formatAttachments(m, bodyIndent)}`;
|
|
876
881
|
}
|
|
877
882
|
/**
|
|
878
883
|
* Read the caller's person/agent uid and email from a JWT we already hold.
|
|
@@ -1227,26 +1232,27 @@ async function runDmThread(identifier, opts) {
|
|
|
1227
1232
|
* `hq channels`). Throws a user-facing message when nothing matches or a name is
|
|
1228
1233
|
* ambiguous.
|
|
1229
1234
|
*/
|
|
1230
|
-
async function
|
|
1235
|
+
async function resolveChannel(token, target) {
|
|
1231
1236
|
const raw = target.trim().replace(/^#/, "");
|
|
1232
1237
|
if (!raw)
|
|
1233
1238
|
throw new Error("A channel name or id is required.");
|
|
1234
1239
|
const channels = await fetchChannels(token);
|
|
1235
1240
|
const named = matchChannelsByName(channels, raw);
|
|
1236
1241
|
if (named.length === 1)
|
|
1237
|
-
return named[0]
|
|
1242
|
+
return named[0];
|
|
1238
1243
|
if (named.length > 1) {
|
|
1239
1244
|
throw new Error(`'${raw}' matches ${named.length} channels — pass the channel id instead (see \`hq channels\`).`);
|
|
1240
1245
|
}
|
|
1241
1246
|
const byId = channels.find((c) => c.channelId === raw);
|
|
1242
1247
|
if (byId)
|
|
1243
|
-
return byId
|
|
1248
|
+
return byId;
|
|
1244
1249
|
throw new Error(`No channel named or with id '${raw}' — run \`hq channels\` to see yours.`);
|
|
1245
1250
|
}
|
|
1246
1251
|
async function runDmChannel(target, opts) {
|
|
1247
1252
|
try {
|
|
1248
1253
|
const token = await ensureCognitoToken();
|
|
1249
|
-
const
|
|
1254
|
+
const channel = await resolveChannel(token, target);
|
|
1255
|
+
const channelId = channel.channelId;
|
|
1250
1256
|
const query = {};
|
|
1251
1257
|
if (opts.limit)
|
|
1252
1258
|
query.limit = opts.limit;
|
|
@@ -1268,7 +1274,17 @@ async function runDmChannel(target, opts) {
|
|
|
1268
1274
|
console.log(JSON.stringify(ordered, null, 2));
|
|
1269
1275
|
}
|
|
1270
1276
|
else if (ordered.length === 0) {
|
|
1271
|
-
|
|
1277
|
+
const channelType = channel.scope === "personal"
|
|
1278
|
+
? "personal"
|
|
1279
|
+
: channel.scope === "group"
|
|
1280
|
+
? "group"
|
|
1281
|
+
: "shared";
|
|
1282
|
+
const channelName = channel.name?.trim() || channel.slug?.trim() || channelId;
|
|
1283
|
+
const requestedTarget = target.trim().replace(/^#/, "");
|
|
1284
|
+
const sendTarget = requestedTarget === channelId
|
|
1285
|
+
? channelId
|
|
1286
|
+
: channel.slug?.trim() || requestedTarget;
|
|
1287
|
+
console.log(chalk.dim(`No messages in ${channelType} channel "${channelName}" yet.\nSend a message with: hq dm ${sendTarget} "Hello"`));
|
|
1272
1288
|
}
|
|
1273
1289
|
else {
|
|
1274
1290
|
console.log(chalk.green(`${ordered.length} message${ordered.length === 1 ? "" : "s"}:`));
|
|
@@ -63,6 +63,11 @@ export function parseCompanySlugFromPath(prefix) {
|
|
|
63
63
|
}
|
|
64
64
|
return parts[1];
|
|
65
65
|
}
|
|
66
|
+
// Normalize personal paths before using them as S3 list prefixes.
|
|
67
|
+
function normalizePersonalBrowsePrefix(pathPrefix) {
|
|
68
|
+
const normalized = pathPrefix.replace(/^\/+/, "");
|
|
69
|
+
return /^[/.]+$/.test(normalized) ? "" : normalized;
|
|
70
|
+
}
|
|
66
71
|
/**
|
|
67
72
|
* Translate a CLI `companies/<slug>/…` path into the company-relative S3 key
|
|
68
73
|
* the vault bucket actually stores. Company vault buckets are already
|
|
@@ -277,10 +282,17 @@ export async function runBrowse(input) {
|
|
|
277
282
|
}
|
|
278
283
|
// Company vault keys are company-relative (no `companies/<slug>/` prefix), so
|
|
279
284
|
// translate the CLI's anchored prefix into the bucket-relative form before
|
|
280
|
-
// listing. Personal-mode paths are
|
|
281
|
-
const
|
|
282
|
-
? pathPrefix
|
|
283
|
-
:
|
|
285
|
+
// listing. Personal-mode paths are normalized in bucket-relative form.
|
|
286
|
+
const bucketPrefix = personalMode
|
|
287
|
+
? normalizePersonalBrowsePrefix(pathPrefix)
|
|
288
|
+
: slug === undefined
|
|
289
|
+
? pathPrefix
|
|
290
|
+
: toBucketRelative(pathPrefix, slug);
|
|
291
|
+
// Company callers can spell the bucket root as `/` or `.`. S3's empty
|
|
292
|
+
// Prefix is the root; passing either literal would list under that key.
|
|
293
|
+
const listPrefix = !personalMode && (bucketPrefix === "/" || bucketPrefix === ".")
|
|
294
|
+
? ""
|
|
295
|
+
: bucketPrefix;
|
|
284
296
|
// Pull the caller's explicit-grant graph once so per-key classification is
|
|
285
297
|
// O(grants) without N round-trips. Retain the raw pattern after stripping
|
|
286
298
|
// only the redundant company anchor: `foo/` and `foo/*` have different
|
|
@@ -220,9 +220,23 @@ export function createLaneMessageHandlers(deps) {
|
|
|
220
220
|
storySwitchDeliveryStarted = true;
|
|
221
221
|
opts.onAccepted?.();
|
|
222
222
|
};
|
|
223
|
+
if (opts.text !== undefined && opts.textFile !== undefined) {
|
|
224
|
+
throw new LanesError("message_text_source_conflict", "Pass either --text or --text-file, not both", { lane_id: laneId });
|
|
225
|
+
}
|
|
223
226
|
let text = opts.text;
|
|
224
|
-
if (
|
|
225
|
-
|
|
227
|
+
if (opts.textFile !== undefined) {
|
|
228
|
+
const textPath = path.isAbsolute(opts.textFile)
|
|
229
|
+
? opts.textFile
|
|
230
|
+
: path.resolve(ctx.callerCwd(), opts.textFile);
|
|
231
|
+
try {
|
|
232
|
+
text = ctx.readFileSync(textPath, "utf8");
|
|
233
|
+
}
|
|
234
|
+
catch (err) {
|
|
235
|
+
throw new LanesError("message_text_file_unreadable", `Could not read message text file ${opts.textFile}: ${err instanceof Error ? err.message : String(err)}`, { lane_id: laneId, path: textPath });
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
if (text === undefined || text.length === 0) {
|
|
239
|
+
throw new LanesError("missing_text", "Pass non-empty message text with --text or --text-file", { lane_id: laneId });
|
|
226
240
|
}
|
|
227
241
|
let resolution;
|
|
228
242
|
try {
|
|
@@ -261,6 +275,7 @@ export function createLaneMessageHandlers(deps) {
|
|
|
261
275
|
: undefined;
|
|
262
276
|
await dispatchOutpostLaneCommand(ctx, lane, "message", {
|
|
263
277
|
...opts,
|
|
278
|
+
text,
|
|
264
279
|
...(supersededMessageCallerId === undefined ? {} : { supersededMessageCallerId }),
|
|
265
280
|
...(supersededMessageSessionId === undefined ? {} : { supersededMessageSessionId }),
|
|
266
281
|
}, {
|
package/dist/commands/lanes.js
CHANGED
|
@@ -33,6 +33,7 @@ import { withCliFeatureUsed } from "../utils/cli-telemetry.js";
|
|
|
33
33
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
34
34
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
35
35
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
36
|
+
import { LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG } from "../lib/lanes/flag-keys.js";
|
|
36
37
|
import { isControllerAliasOutpostId } from "../lib/lanes/admission-host.js";
|
|
37
38
|
import { runLeadAutopilotPass } from "../lib/lanes/autopilot.js";
|
|
38
39
|
import { syncOutpostLeadRegistries } from "../lib/lanes/autopilot-registry.js";
|
|
@@ -1197,14 +1198,20 @@ async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOpt
|
|
|
1197
1198
|
throw new LanesError("outpost_lane_list_unavailable", `Could not read lanes from Outpost ${outpostId}: ${message}`, { outpost_id: outpostId });
|
|
1198
1199
|
}
|
|
1199
1200
|
if (result.done !== true || result.status !== "Success" || result.exitCode !== 0) {
|
|
1200
|
-
throw new LanesError("outpost_lane_list_unavailable", `Outpost ${outpostId} did not return a successful lane list`, {
|
|
1201
|
+
throw new LanesError("outpost_lane_list_unavailable", `Outpost ${outpostId} did not return a successful lane list`, {
|
|
1202
|
+
outpost_id: outpostId,
|
|
1203
|
+
command_id: result.commandId,
|
|
1204
|
+
status: result.status,
|
|
1205
|
+
done: result.done ?? false,
|
|
1206
|
+
exit_code: result.exitCode,
|
|
1207
|
+
});
|
|
1201
1208
|
}
|
|
1202
1209
|
let body;
|
|
1203
1210
|
try {
|
|
1204
1211
|
body = JSON.parse(result.stdout);
|
|
1205
1212
|
}
|
|
1206
1213
|
catch (err) {
|
|
1207
|
-
throw new LanesError("outpost_lane_list_invalid", `Outpost ${outpostId} returned invalid lane-list JSON: ${dispatchErrorMessage(err)}`, { outpost_id: outpostId });
|
|
1214
|
+
throw new LanesError("outpost_lane_list_invalid", `Outpost ${outpostId} returned invalid lane-list JSON: ${dispatchErrorMessage(err)}`, { outpost_id: outpostId, command_id: result.commandId });
|
|
1208
1215
|
}
|
|
1209
1216
|
if (body === null || typeof body !== "object" || Array.isArray(body) ||
|
|
1210
1217
|
body.ok !== true ||
|
|
@@ -1212,6 +1219,7 @@ async function readOutpostLaneRecordsUncached(ctx, outpostId, query, dispatchOpt
|
|
|
1212
1219
|
!Array.isArray(body.lanes)) {
|
|
1213
1220
|
throw new LanesError("outpost_lane_list_invalid", `Outpost ${outpostId} returned a malformed lane list`, {
|
|
1214
1221
|
outpost_id: outpostId,
|
|
1222
|
+
command_id: result.commandId,
|
|
1215
1223
|
});
|
|
1216
1224
|
}
|
|
1217
1225
|
const listed = body.lanes;
|
|
@@ -1499,7 +1507,12 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1499
1507
|
}
|
|
1500
1508
|
const separator = laneId.lastIndexOf("_");
|
|
1501
1509
|
const hostSuffix = separator < 0 ? "" : laneId.slice(separator + 1);
|
|
1502
|
-
|
|
1510
|
+
const ownerCompany = !hostSuffix && requestedUlid !== undefined && options.allowHostlessOwnerLookup
|
|
1511
|
+
? await options.allowHostlessOwnerLookup()
|
|
1512
|
+
: undefined;
|
|
1513
|
+
const hostlessUlidLookup = ownerCompany !== undefined;
|
|
1514
|
+
if ((!hostSuffix && !hostlessUlidLookup) ||
|
|
1515
|
+
(hostSuffix && sanitizeHostComponent(hostSuffix) !== hostSuffix)) {
|
|
1503
1516
|
throw commandLaneNotFound(laneId, options.outpostHint === true);
|
|
1504
1517
|
}
|
|
1505
1518
|
let rawOutpostRoutes;
|
|
@@ -1542,7 +1555,9 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1542
1555
|
if (requestedOutpostId !== undefined && !outpostIds.includes(requestedOutpostId)) {
|
|
1543
1556
|
throw new LanesError("outpost_not_registered", `Outpost ${requestedOutpostId} is not registered`, { lane_id: laneId, outpost_id: requestedOutpostId });
|
|
1544
1557
|
}
|
|
1545
|
-
const hostRoutes =
|
|
1558
|
+
const hostRoutes = hostlessUlidLookup
|
|
1559
|
+
? []
|
|
1560
|
+
: outpostRoutes.filter((route) => outpostRouteMatchesHost(route, hostSuffix));
|
|
1546
1561
|
const identity = ctx.readOutpostIdentity();
|
|
1547
1562
|
const localHostRoutes = outpostRoutes.filter((route) => outpostRouteMatchesHost(route, ctx.hostname()));
|
|
1548
1563
|
if (localHostRoutes.length > 1 || (identity.kind === "id" && localHostRoutes.some((route) => route.outpostId !== identity.id))) {
|
|
@@ -1553,20 +1568,22 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1553
1568
|
const hostOutpostIdSet = new Set(hostOutpostIds);
|
|
1554
1569
|
const matches = [];
|
|
1555
1570
|
const failedOutpostIds = [];
|
|
1571
|
+
const failedOutpostCommands = [];
|
|
1556
1572
|
const probedOutpostIds = [];
|
|
1557
1573
|
const lookupOutpostIds = requestedOutpostId !== undefined
|
|
1558
1574
|
? [requestedOutpostId]
|
|
1559
|
-
:
|
|
1560
|
-
|
|
1561
|
-
|
|
1562
|
-
|
|
1575
|
+
: hostlessUlidLookup
|
|
1576
|
+
? outpostIds.filter((id) => id !== localOutpostId)
|
|
1577
|
+
: [
|
|
1578
|
+
...hostOutpostIds.filter((id) => id !== localOutpostId),
|
|
1579
|
+
...outpostIds.filter((id) => id !== localOutpostId && !hostOutpostIdSet.has(id)),
|
|
1580
|
+
];
|
|
1563
1581
|
for (const outpostId of lookupOutpostIds) {
|
|
1564
1582
|
let ownerLanes;
|
|
1565
1583
|
try {
|
|
1566
|
-
ownerLanes = await readOutpostLaneRecords(ctx, outpostId,
|
|
1567
|
-
|
|
1568
|
-
snapshot: true
|
|
1569
|
-
});
|
|
1584
|
+
ownerLanes = await readOutpostLaneRecords(ctx, outpostId, hostlessUlidLookup
|
|
1585
|
+
? { snapshot: true }
|
|
1586
|
+
: { hierarchy: [laneId], snapshot: true });
|
|
1570
1587
|
probedOutpostIds.push(outpostId);
|
|
1571
1588
|
}
|
|
1572
1589
|
catch (err) {
|
|
@@ -1575,16 +1592,38 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1575
1592
|
if (isLanesError(err) && err.error === "outpost_wait_invalid")
|
|
1576
1593
|
throw err;
|
|
1577
1594
|
failedOutpostIds.push(outpostId);
|
|
1595
|
+
failedOutpostCommands.push({
|
|
1596
|
+
outpost_id: outpostId,
|
|
1597
|
+
...(isLanesError(err) && typeof err.details.command_id === "string"
|
|
1598
|
+
? { command_id: err.details.command_id }
|
|
1599
|
+
: {}),
|
|
1600
|
+
});
|
|
1578
1601
|
continue;
|
|
1579
1602
|
}
|
|
1580
1603
|
for (const lane of ownerLanes) {
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1604
|
+
const recordedSuffix = lane.lane_id.slice(lane.lane_id.lastIndexOf("_") + 1);
|
|
1605
|
+
const laneCompany = hostlessUlidLookup
|
|
1606
|
+
? resolveLaneCompany(ctx.hqRoot(), lane).company
|
|
1607
|
+
: undefined;
|
|
1608
|
+
const companyMatches = !hostlessUlidLookup || (ownerCompany !== undefined &&
|
|
1609
|
+
laneCompany?.uid === ownerCompany.uid &&
|
|
1610
|
+
laneCompany.slug === ownerCompany.slug);
|
|
1611
|
+
if (lane.host.kind === "local" && ((!hostlessUlidLookup && lane.lane_id === laneId &&
|
|
1612
|
+
sanitizeHostComponent(lane.host.id) === hostSuffix) ||
|
|
1613
|
+
(hostlessUlidLookup && laneUlidPart(lane.lane_id) === requestedUlid &&
|
|
1614
|
+
sanitizeHostComponent(lane.host.id) === recordedSuffix &&
|
|
1615
|
+
companyMatches))) {
|
|
1584
1616
|
matches.push({ outpostId, lane });
|
|
1585
1617
|
}
|
|
1586
1618
|
}
|
|
1587
1619
|
}
|
|
1620
|
+
if (hostlessUlidLookup && failedOutpostIds.length > 0) {
|
|
1621
|
+
throw new LanesError("lane_owner_lookup_unavailable", `Could not verify whether lane ${laneId} is registered on every Outpost`, {
|
|
1622
|
+
lane_id: laneId,
|
|
1623
|
+
failed_outpost_ids: failedOutpostIds,
|
|
1624
|
+
remote_commands: failedOutpostCommands,
|
|
1625
|
+
});
|
|
1626
|
+
}
|
|
1588
1627
|
if (matches.length === 0) {
|
|
1589
1628
|
throw commandLaneNotFound(laneId, options.outpostHint === true, {
|
|
1590
1629
|
...(failedOutpostIds.length > 0 ? { failed_outpost_ids: failedOutpostIds } : {}),
|
|
@@ -1604,9 +1643,9 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1604
1643
|
}
|
|
1605
1644
|
function laneUlidPart(laneId) {
|
|
1606
1645
|
const separator = laneId.indexOf("_");
|
|
1607
|
-
if (separator !== 26)
|
|
1646
|
+
if (separator !== -1 && separator !== 26)
|
|
1608
1647
|
return undefined;
|
|
1609
|
-
const ulid = laneId.slice(0, separator);
|
|
1648
|
+
const ulid = separator === -1 ? laneId : laneId.slice(0, separator);
|
|
1610
1649
|
return /^[0-9A-HJKMNP-TV-Z]{26}$/i.test(ulid) ? ulid.toUpperCase() : undefined;
|
|
1611
1650
|
}
|
|
1612
1651
|
function commandLaneNotFound(laneId, outpostHint, details = {}) {
|
|
@@ -7660,7 +7699,7 @@ function writeAnswerDeliveryFailed(hqRoot, laneId, questionId, err, at) {
|
|
|
7660
7699
|
async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
7661
7700
|
const fromEnv = ctx.env.HQ_ROOT?.trim();
|
|
7662
7701
|
const hqRoot = fromEnv && ctx.existsSync(fromEnv) ? fromEnv : ctx.hqRoot();
|
|
7663
|
-
const mirrorRefresh = opts.dryRun
|
|
7702
|
+
const mirrorRefresh = opts.dryRun || opts.terminalKeepAliveCandidate
|
|
7664
7703
|
? {
|
|
7665
7704
|
updated: new Map(),
|
|
7666
7705
|
unverifiedRemote: new Map(),
|
|
@@ -7724,6 +7763,9 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
|
7724
7763
|
recoverQueuedMessages,
|
|
7725
7764
|
mesh: ctx.mesh,
|
|
7726
7765
|
env: ctx.env,
|
|
7766
|
+
...(opts.terminalKeepAliveCandidate !== undefined
|
|
7767
|
+
? { terminalKeepAliveCandidate: opts.terminalKeepAliveCandidate }
|
|
7768
|
+
: {}),
|
|
7727
7769
|
prepareKeepAliveClaudeResume: async (lane) => {
|
|
7728
7770
|
const recoveredLane = laneWithRecoveredProviderSession(hqRoot, lane);
|
|
7729
7771
|
const pendingModel = recoveredLane.pending_settings?.model;
|
|
@@ -8041,7 +8083,16 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8041
8083
|
persistClaudeAccountSelection,
|
|
8042
8084
|
printJson,
|
|
8043
8085
|
recordResumeSetup,
|
|
8044
|
-
resolveCommandLane: (commandCtx, laneId) => resolveCommandLaneTarget(commandCtx, laneId
|
|
8086
|
+
resolveCommandLane: (commandCtx, laneId) => resolveCommandLaneTarget(commandCtx, laneId, {
|
|
8087
|
+
allowHostlessOwnerLookup: async () => {
|
|
8088
|
+
const company = await commandCtx.resolveCompany();
|
|
8089
|
+
const enabled = await readLanesRegistryFlag(LANE_MESSAGE_ROUTE_OWNER_HOST_FLAG, {
|
|
8090
|
+
company,
|
|
8091
|
+
env: commandCtx.env,
|
|
8092
|
+
});
|
|
8093
|
+
return enabled ? company : undefined;
|
|
8094
|
+
},
|
|
8095
|
+
}),
|
|
8045
8096
|
resolveActiveQuestionCompany,
|
|
8046
8097
|
hasPendingQuestionForLane: questionHandlers.hasPendingQuestionForLane,
|
|
8047
8098
|
cancelFallbackQuestionsForLane: questionHandlers.cancelFallbackQuestionsForLane,
|
|
@@ -8406,7 +8457,8 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8406
8457
|
.command("message")
|
|
8407
8458
|
.description("Send a follow-up message to a lane")
|
|
8408
8459
|
.argument("<lane-id>", "Lane id")
|
|
8409
|
-
.
|
|
8460
|
+
.option("--text <msg>", "Message text")
|
|
8461
|
+
.option("--text-file <path>", "Read message text from a file")
|
|
8410
8462
|
.option("--project <slug>", "Re-point project attribution; a changed story is named in the resumed prompt")
|
|
8411
8463
|
.option("--story <id>", "Re-point story attribution; a changed story is named in the resumed prompt")
|
|
8412
8464
|
.option("--account <id>", "Move the lane's session to this account of its provider before the message resumes it")
|
|
@@ -8745,6 +8797,30 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8745
8797
|
await messageHandlers.recoverQueuedMessages(ctx, lane.lane_id);
|
|
8746
8798
|
});
|
|
8747
8799
|
}));
|
|
8800
|
+
lanes
|
|
8801
|
+
.command("_keep-alive-terminal-trigger <laneId> <version> <dueAt>", { hidden: true })
|
|
8802
|
+
.action(wrap(async (laneId, versionText, dueAt) => {
|
|
8803
|
+
const version = Number.parseInt(versionText, 10);
|
|
8804
|
+
if (!Number.isSafeInteger(version) || version < 1 || String(version) !== versionText) {
|
|
8805
|
+
throw new LanesError("invalid_lane_version", "Terminal keep-alive trigger requires a positive lane version", {
|
|
8806
|
+
lane_id: laneId,
|
|
8807
|
+
version: versionText,
|
|
8808
|
+
});
|
|
8809
|
+
}
|
|
8810
|
+
const dueAtMs = Date.parse(dueAt);
|
|
8811
|
+
if (!Number.isFinite(dueAtMs)) {
|
|
8812
|
+
throw new LanesError("invalid_terminal_trigger_time", "Terminal keep-alive trigger requires a valid due time", {
|
|
8813
|
+
lane_id: laneId,
|
|
8814
|
+
due_at: dueAt,
|
|
8815
|
+
});
|
|
8816
|
+
}
|
|
8817
|
+
const delayMs = dueAtMs - Date.now();
|
|
8818
|
+
if (delayMs > 0)
|
|
8819
|
+
await new Promise((resolve) => setTimeout(resolve, delayMs));
|
|
8820
|
+
await runReconcile(ctx, { terminalKeepAliveCandidate: { laneId, version } }, async (lane) => {
|
|
8821
|
+
await messageHandlers.recoverQueuedMessages(ctx, lane.lane_id);
|
|
8822
|
+
});
|
|
8823
|
+
}));
|
|
8748
8824
|
reconcile
|
|
8749
8825
|
.command("install")
|
|
8750
8826
|
.description("Write systemd user unit and timer files (idempotent)")
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { resolveCompanyFlagGate, resolveProcessFlagGate } from "../lib/flag-registry.js";
|
|
2
|
+
export const VAULT_RETENTION_SETTINGS_FLAG = "cli.vault-retention-settings";
|
|
3
|
+
export function vaultRetentionSettingsEnabled(companyUid, idToken) {
|
|
4
|
+
if (companyUid && idToken) {
|
|
5
|
+
return resolveCompanyFlagGate(companyUid, idToken, VAULT_RETENTION_SETTINGS_FLAG, { fallback: false }, () => false);
|
|
6
|
+
}
|
|
7
|
+
return resolveProcessFlagGate(VAULT_RETENTION_SETTINGS_FLAG, { fallback: false }, () => false);
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=vault-retention-flag.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { vaultApiFetch } from "../utils/vault-api.js";
|
|
3
|
+
type Dependencies = {
|
|
4
|
+
enabled: (companyUid?: string, token?: string) => boolean | Promise<boolean>;
|
|
5
|
+
token: () => Promise<string>;
|
|
6
|
+
companyUid: (token: string, slug: string | undefined) => Promise<string>;
|
|
7
|
+
request: typeof vaultApiFetch;
|
|
8
|
+
};
|
|
9
|
+
export declare function registerVaultRetentionCommand(program: Command, deps?: Dependencies): void;
|
|
10
|
+
export {};
|
|
11
|
+
//# sourceMappingURL=vault-retention.d.ts.map
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
2
|
+
import { markExpectedUserError } from "../utils/expected-cli-error.js";
|
|
3
|
+
import { getCompanyUid, vaultApiFetch } from "../utils/vault-api.js";
|
|
4
|
+
import { vaultRetentionSettingsEnabled } from "./vault-retention-flag.js";
|
|
5
|
+
const defaultDependencies = {
|
|
6
|
+
enabled: vaultRetentionSettingsEnabled,
|
|
7
|
+
token: ensureCognitoToken,
|
|
8
|
+
companyUid: getCompanyUid,
|
|
9
|
+
request: vaultApiFetch,
|
|
10
|
+
};
|
|
11
|
+
export function registerVaultRetentionCommand(program, deps = defaultDependencies) {
|
|
12
|
+
const vault = program.command("vault").description("Manage HQ vault settings");
|
|
13
|
+
const retention = vault
|
|
14
|
+
.command("retention")
|
|
15
|
+
.description("Get or set the default retention for future vault object versions")
|
|
16
|
+
.option("--company <slug>", "Company vault slug (omit for your personal vault)");
|
|
17
|
+
const companyOf = (command) => command.opts().company ??
|
|
18
|
+
retention.opts().company;
|
|
19
|
+
retention
|
|
20
|
+
.command("get")
|
|
21
|
+
.description("Show the vault's default retention in days")
|
|
22
|
+
.action(async function () {
|
|
23
|
+
await execute(this, "GET");
|
|
24
|
+
});
|
|
25
|
+
retention
|
|
26
|
+
.command("set")
|
|
27
|
+
.description("Set the vault's default retention in days (1-30)")
|
|
28
|
+
.argument("<days>")
|
|
29
|
+
.action(async function (rawDays) {
|
|
30
|
+
const days = Number(rawDays);
|
|
31
|
+
if (!Number.isInteger(days) || days < 1 || days > 30) {
|
|
32
|
+
throw new Error("days must be an integer from 1 through 30");
|
|
33
|
+
}
|
|
34
|
+
await execute(this, "PUT", days);
|
|
35
|
+
});
|
|
36
|
+
async function execute(command, method, days) {
|
|
37
|
+
const slug = companyOf(command);
|
|
38
|
+
let token;
|
|
39
|
+
let companyUid;
|
|
40
|
+
if (slug) {
|
|
41
|
+
token = await deps.token();
|
|
42
|
+
companyUid = await deps.companyUid(token, slug);
|
|
43
|
+
if (!(await deps.enabled(companyUid, token))) {
|
|
44
|
+
throw markExpectedUserError(new Error("Vault retention settings are not enabled for this company."));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
if (!(await deps.enabled())) {
|
|
49
|
+
throw markExpectedUserError(new Error("Vault retention settings are not enabled for this company."));
|
|
50
|
+
}
|
|
51
|
+
token = await deps.token();
|
|
52
|
+
}
|
|
53
|
+
const query = companyUid ? { companyUid } : undefined;
|
|
54
|
+
const response = await deps.request({
|
|
55
|
+
token,
|
|
56
|
+
path: "/vault/retention",
|
|
57
|
+
method,
|
|
58
|
+
...(query ? { query } : {}),
|
|
59
|
+
...(days === undefined ? {} : { body: { days } }),
|
|
60
|
+
});
|
|
61
|
+
if (!response.ok) {
|
|
62
|
+
const body = (await response.json().catch(() => ({})));
|
|
63
|
+
throw new Error(body.error ?? body.message ?? response.statusText);
|
|
64
|
+
}
|
|
65
|
+
const body = (await response.json());
|
|
66
|
+
process.stdout.write(`${body.days} days\n`);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=vault-retention.js.map
|
package/dist/fast-core.js
CHANGED
|
@@ -59,12 +59,13 @@ export async function runFastCore(argv) {
|
|
|
59
59
|
if (typeof handler !== "function") {
|
|
60
60
|
throw new Error(`Native fast handler ${entry.exportName} was not exported`);
|
|
61
61
|
}
|
|
62
|
+
const readsStdin = entry.readsStdinFor?.(parsed.args) ?? entry.readsStdin ?? false;
|
|
62
63
|
const chunks = [];
|
|
63
|
-
if (
|
|
64
|
+
if (readsStdin) {
|
|
64
65
|
for await (const chunk of process.stdin)
|
|
65
66
|
chunks.push(chunk);
|
|
66
67
|
}
|
|
67
|
-
const code = await handler(parsed.args,
|
|
68
|
+
const code = await handler(parsed.args, readsStdin ? Buffer.concat(chunks).toString("utf8") : "", process.cwd(), hqRoot, entry.name.split(" ").at(-1) ?? entry.name, parsed.hqRootArg);
|
|
68
69
|
// Let Node drain handler output before terminating. The timeout guard's
|
|
69
70
|
// stderr message must survive the native route's process boundary.
|
|
70
71
|
process.exitCode = code;
|
|
@@ -80,12 +81,18 @@ export async function runFastCore(argv) {
|
|
|
80
81
|
: process.cwd();
|
|
81
82
|
const injectRoot = entry.kind === "shell" || entry.kind === "native" &&
|
|
82
83
|
(entry.shellHqRoot === "live" || entry.shellHqRoot === "reconcile-default" && shouldInjectRoot);
|
|
84
|
+
const childEnv = {
|
|
85
|
+
...(entry.name === "hq-session" ? { HQ_HQ_SESSION_NO_CLI: "1" } : {}),
|
|
86
|
+
...(entry.name === "migrate-policy-triggers"
|
|
87
|
+
? { CLAUDE_PROJECT_DIR: process.env.CLAUDE_PROJECT_DIR || process.cwd() }
|
|
88
|
+
: {}),
|
|
89
|
+
};
|
|
83
90
|
const { code } = runBundledScript({
|
|
84
91
|
asset,
|
|
85
92
|
args: parsed.args,
|
|
86
93
|
cwd,
|
|
87
94
|
...(injectRoot ? { hqRoot } : {}),
|
|
88
|
-
|
|
95
|
+
...(Object.keys(childEnv).length ? { env: childEnv } : {}),
|
|
89
96
|
});
|
|
90
97
|
// Propagate verbatim: these scripts use their exit code as their interface.
|
|
91
98
|
process.exit(code);
|
package/dist/fast-scaffold.d.ts
CHANGED
|
@@ -40,6 +40,8 @@ export type FastScaffoldScript = {
|
|
|
40
40
|
shellHqRoot?: "none" | "live" | "reconcile-default";
|
|
41
41
|
/** Whether this handler consumes stdin. Shell companions inherit stdin directly. */
|
|
42
42
|
readsStdin?: boolean;
|
|
43
|
+
/** Per-invocation stdin policy for handlers whose shell reads conditionally. */
|
|
44
|
+
readsStdinFor?: (args: readonly string[]) => boolean;
|
|
43
45
|
/** Whether the bundled fast-route module closure needs Node's network timeout patch. */
|
|
44
46
|
requiresNodeNetworkCompatibility?: boolean;
|
|
45
47
|
};
|
package/dist/fast-scaffold.js
CHANGED
|
@@ -184,6 +184,17 @@ export const FAST_SCAFFOLD_SCRIPTS = [
|
|
|
184
184
|
shellCwd: "live",
|
|
185
185
|
shellHqRoot: "live",
|
|
186
186
|
},
|
|
187
|
+
...[
|
|
188
|
+
{ name: "eval-trigger", modulePath: "./commands/core-eval-trigger-native.js", exportName: "runEvalTriggerFast", asset: "core/scripts/eval-trigger.sh", root: "cwd", shellCwd: "caller", shellHqRoot: "none", readsStdinFor: (args) => args[0] === "--check" },
|
|
189
|
+
{ name: "migrate-policy-triggers", modulePath: "./commands/core-migrate-policy-triggers-native.js", exportName: "runMigratePolicyTriggersFast", asset: "core/scripts/migrate-policy-triggers.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
|
|
190
|
+
{ name: "register-project", modulePath: "./commands/core-register-project-native.js", exportName: "runRegisterProjectFast", asset: "core/scripts/register-project.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
|
|
191
|
+
{ name: "work-mesh-live-rebind", modulePath: "./commands/core-work-mesh-live-rebind-native.js", exportName: "runWorkMeshLiveRebindFast", asset: "core/scripts/work-mesh-live-rebind.sh", root: "live", shellCwd: "caller", shellHqRoot: "live" },
|
|
192
|
+
].map((entry) => ({
|
|
193
|
+
...entry,
|
|
194
|
+
kind: "native",
|
|
195
|
+
flagKey: "hq-core-native-fast-route",
|
|
196
|
+
familyFlagKey: "core-native-hook-helpers",
|
|
197
|
+
})),
|
|
187
198
|
...["resolve", "pickup", "repo", "secrets", "transfer"].map((name) => ({
|
|
188
199
|
name: `delegate ${name}`,
|
|
189
200
|
kind: "native",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
export declare const AGENT_MODEL_PROVIDERS: readonly ["codex", "grok", "claude"];
|
|
2
2
|
export type AgentModelProvider = (typeof AGENT_MODEL_PROVIDERS)[number];
|
|
3
|
-
|
|
3
|
+
interface AgentCatalogModel {
|
|
4
4
|
id: string;
|
|
5
5
|
label: string;
|
|
6
6
|
visibility: string;
|
|
@@ -10,7 +10,7 @@ export interface AgentCatalogModel {
|
|
|
10
10
|
selectable: boolean;
|
|
11
11
|
unavailableReason?: string | null;
|
|
12
12
|
}
|
|
13
|
-
|
|
13
|
+
interface AgentCatalogProvider {
|
|
14
14
|
models: AgentCatalogModel[];
|
|
15
15
|
defaultModel: string;
|
|
16
16
|
}
|
|
@@ -58,4 +58,5 @@ export declare function validateAgentModelSelection(catalog: AgentModelCatalog,
|
|
|
58
58
|
export declare function modelSelectionErrorMessage(modelId: string, result: Extract<ModelSelectionResult, {
|
|
59
59
|
ok: false;
|
|
60
60
|
}>): string;
|
|
61
|
+
export {};
|
|
61
62
|
//# sourceMappingURL=model-catalog.d.ts.map
|
|
@@ -1,12 +1,4 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
|
-
export interface CompanySkillWrapperOwnershipDiagnostics {
|
|
3
|
-
kind: "directory" | "symlink" | "file" | "other";
|
|
4
|
-
bridgeState: "all_live" | "partial" | "none" | "empty" | "not_applicable";
|
|
5
|
-
markerState: "bound" | "missing" | "invalid" | "unbound" | "unavailable" | "not_applicable";
|
|
6
|
-
linkTarget: "company_skills" | "outside_company_skills" | "unavailable" | "not_applicable";
|
|
7
|
-
platform: "win32" | "darwin" | "linux" | "other";
|
|
8
|
-
nameEncoding: "percent_encoded" | "literal";
|
|
9
|
-
}
|
|
10
2
|
export declare function companySkillWrapperSentryTagsFor(error: unknown): Record<string, string> | undefined;
|
|
11
3
|
export interface SurfaceCompanySkillInput {
|
|
12
4
|
hqRoot: string;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
|
|
1
|
+
type BridgeCommand = "status" | "install" | "doctor";
|
|
2
2
|
export type BridgeOptions = {
|
|
3
3
|
command: BridgeCommand;
|
|
4
4
|
hqRoot: string;
|
|
@@ -28,4 +28,5 @@ export declare function isHqRootLike(candidate: string): boolean;
|
|
|
28
28
|
export declare function outputStyleSlug(name: string): string;
|
|
29
29
|
/** Runs one bridge command; returns the process exit code. */
|
|
30
30
|
export declare function runCodexSkillBridge(options: BridgeOptions, io: BridgeIo): number;
|
|
31
|
+
export {};
|
|
31
32
|
//# sourceMappingURL=codex-skill-bridge.d.ts.map
|