@indigoai-us/hq-cli 5.280.0 → 5.281.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 +16 -0
- package/assets/scaffold/core/scripts/knowledge-prefs.sh +55 -0
- package/assets/scaffold/core/scripts/ontology-audience-grant.sh +81 -0
- package/assets/scaffold/core/scripts/ontology-brief.mjs +93 -0
- package/assets/scaffold/core/scripts/ontology-candidate.sh +100 -0
- package/assets/scaffold/core/scripts/ontology-garden.mjs +238 -0
- package/assets/scaffold/core/scripts/ontology-source-worklist.sh +79 -0
- package/assets/scaffold/core/scripts/source-yaml-validate.sh +36 -0
- package/dist/command-catalog.generated.d.ts +80 -2
- package/dist/command-catalog.generated.js +98 -2
- package/dist/commands/auth.js +40 -2
- package/dist/commands/core-ontology-native.d.ts +16 -0
- package/dist/commands/core-ontology-native.js +924 -0
- package/dist/commands/core-ontology.js +11 -0
- package/dist/commands/core.js +9 -0
- package/dist/commands/group-grants.js +3 -2
- package/dist/commands/lanes.d.ts +1 -0
- package/dist/commands/lanes.js +253 -6
- package/dist/lib/lanes/outpost-dispatch.d.ts +3 -1
- package/dist/lib/lanes/outpost-dispatch.js +4 -0
- package/dist/lib/lanes/store.js +11 -0
- package/dist/lib/lanes/types.d.ts +4 -0
- package/dist/lib/lanes/types.js +1 -1
- package/package.json +1 -1
|
@@ -20,6 +20,7 @@ import { buildEntityIndex } from "../lib/checkpoint-ontology/tree.js";
|
|
|
20
20
|
import { MERGE_COPIES_FLAG, mergeCopies, } from "../lib/checkpoint-ontology/merge-copies.js";
|
|
21
21
|
import { callDeleteEndpoint } from "./files.js";
|
|
22
22
|
import { resolveVaultCredential } from "../utils/resolve-vault-credential.js";
|
|
23
|
+
import { registerCoreOntologyNativeCommands } from "./core-ontology-native.js";
|
|
23
24
|
export const ONTOLOGY_PAYLOAD_FILE = "payload.json";
|
|
24
25
|
class OntologyUsageError extends Error {
|
|
25
26
|
}
|
|
@@ -267,5 +268,15 @@ export function registerCoreOntologyCommand(core) {
|
|
|
267
268
|
}
|
|
268
269
|
});
|
|
269
270
|
describeCoreCommand(apply, { execution: "native", root: "live" });
|
|
271
|
+
registerCoreOntologyNativeCommands(ontology, () => {
|
|
272
|
+
const argv = process.argv.slice(2);
|
|
273
|
+
const coreIndex = argv.indexOf("core");
|
|
274
|
+
const ontologyIndex = argv.indexOf("ontology", coreIndex + 1);
|
|
275
|
+
const rootOptionIndex = argv.indexOf("--hq-root", coreIndex + 1);
|
|
276
|
+
const groupRoot = rootOptionIndex >= 0 && (ontologyIndex < 0 || rootOptionIndex < ontologyIndex)
|
|
277
|
+
? argv[rootOptionIndex + 1]
|
|
278
|
+
: undefined;
|
|
279
|
+
return groupRoot ? rootFor({ hqRoot: groupRoot }) : process.env.HQ_ROOT || rootFor({});
|
|
280
|
+
});
|
|
270
281
|
}
|
|
271
282
|
//# sourceMappingURL=core-ontology.js.map
|
package/dist/commands/core.js
CHANGED
|
@@ -222,6 +222,15 @@ export const SCAFFOLD_ONLY_ASSETS = [
|
|
|
222
222
|
{ asset: "core/scripts/hq-delegate-secrets.sh", root: "live", summary: "Rollback shell for native delegate secrets" },
|
|
223
223
|
{ asset: "core/scripts/hq-delegate-transfer.sh", root: "live", summary: "Rollback shell for native delegate transfer" },
|
|
224
224
|
{ asset: "core/scripts/lib/secret-patterns.sh", root: "live", summary: "Secret-pattern library sourced by the delegate rollback shells" },
|
|
225
|
+
// Retained rollback companions for the gated native ontology commands. These
|
|
226
|
+
// are loose HQ-tree assets, but `hq core ontology` dispatches natively.
|
|
227
|
+
{ asset: "core/scripts/ontology-audience-grant.sh", root: "live", summary: "Rollback companion for native ontology audience grant" },
|
|
228
|
+
{ asset: "core/scripts/ontology-brief.mjs", root: "live", summary: "Rollback companion for native ontology brief" },
|
|
229
|
+
{ asset: "core/scripts/ontology-candidate.sh", root: "live", summary: "Rollback companion for native ontology candidate" },
|
|
230
|
+
{ asset: "core/scripts/ontology-garden.mjs", root: "live", summary: "Rollback companion for native ontology garden" },
|
|
231
|
+
{ asset: "core/scripts/ontology-source-worklist.sh", root: "live", summary: "Rollback companion for native ontology source worklist" },
|
|
232
|
+
{ asset: "core/scripts/source-yaml-validate.sh", root: "cwd", summary: "Rollback companion for native source YAML validation" },
|
|
233
|
+
{ asset: "core/scripts/knowledge-prefs.sh", root: "live", summary: "Sibling dependency for ontology candidate rollback companion" },
|
|
225
234
|
...NATIVE_UTILITY_COMMANDS.map(({ asset, root, summary }) => ({
|
|
226
235
|
asset,
|
|
227
236
|
root,
|
|
@@ -242,8 +242,9 @@ export function registerGroupGrantsCommand(program) {
|
|
|
242
242
|
});
|
|
243
243
|
grants
|
|
244
244
|
.command("outbound")
|
|
245
|
-
.description("List grants a source-company group holds on other companies"
|
|
246
|
-
|
|
245
|
+
.description("List grants a source-company group holds on other companies; " +
|
|
246
|
+
"requires --group <groupId>.")
|
|
247
|
+
.requiredOption("--group <groupId>", "Group id to inspect (required)")
|
|
247
248
|
.action(async (opts) => {
|
|
248
249
|
try {
|
|
249
250
|
const token = await ensureCognitoToken();
|
package/dist/commands/lanes.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ export { spawnLaneWorker };
|
|
|
25
25
|
export { questionBodyAbsPath };
|
|
26
26
|
export { MAX_LANE_DEPTH };
|
|
27
27
|
export declare const LANES_RUNS_REL: string;
|
|
28
|
+
export declare const LANE_REMOTE_MIRROR_SYNC_FLAG = "lanes.remote-done-mirror";
|
|
28
29
|
/** The lane → mesh seam. Defined in lib/lanes/mesh-events.ts; the watcher shares it. */
|
|
29
30
|
export { emitLaneDecisionEvents };
|
|
30
31
|
export interface LanesCommandDeps {
|
package/dist/commands/lanes.js
CHANGED
|
@@ -61,6 +61,7 @@ import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
|
|
|
61
61
|
import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
|
|
62
62
|
import { parseCliEffort, resolveLaneEffort, } from "../lib/lanes/effort.js";
|
|
63
63
|
import { LanesError } from "../lib/lanes/errors.js";
|
|
64
|
+
import { readLatestEnvelopeRecord } from "../lib/lanes/watcher.js";
|
|
64
65
|
import { loadQuotaState, quotaBlockFor } from "../lib/lanes/quota.js";
|
|
65
66
|
import { SESSION_MOVE_REASON } from "../lib/lanes/move-account.js";
|
|
66
67
|
import { managerStatusGateEnabledForLane, resolveManagerStatusGateForLane, } from "../lib/lanes/manager-status-gate.js";
|
|
@@ -74,6 +75,8 @@ export { spawnLaneWorker };
|
|
|
74
75
|
export { questionBodyAbsPath };
|
|
75
76
|
export { MAX_LANE_DEPTH };
|
|
76
77
|
export const LANES_RUNS_REL = path.join("workspace", "lanes-runs");
|
|
78
|
+
export const LANE_REMOTE_MIRROR_SYNC_FLAG = "lanes.remote-done-mirror";
|
|
79
|
+
const REMOTE_MIRROR_REFRESH_BATCH_SIZE = 100;
|
|
77
80
|
/** The lane → mesh seam. Defined in lib/lanes/mesh-events.ts; the watcher shares it. */
|
|
78
81
|
export { emitLaneDecisionEvents };
|
|
79
82
|
function withoutProviderHome(value) {
|
|
@@ -714,7 +717,7 @@ async function dispatchOutpostLaneList(ctx, outpostId, query, timeoutSeconds, wa
|
|
|
714
717
|
timeoutSeconds,
|
|
715
718
|
waitMs,
|
|
716
719
|
});
|
|
717
|
-
if (!rejectedLaneListFlag(filtered, query))
|
|
720
|
+
if (query.strictSnapshot || !rejectedLaneListFlag(filtered, query))
|
|
718
721
|
return filtered;
|
|
719
722
|
return ctx.dispatchOutpostExec({
|
|
720
723
|
outpostId,
|
|
@@ -874,6 +877,7 @@ async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
|
|
|
874
877
|
!listLaneIds(hqRoot).includes(lane.senior.id)) {
|
|
875
878
|
throw new LanesError("hire_grant_context_required", `Resume of Outpost lane ${lane.lane_id} requires the primary senior's hire-grant context`, { lane_id: lane.lane_id, senior_lane_id: lane.senior.id });
|
|
876
879
|
}
|
|
880
|
+
await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot), new Set([lane.senior.id]));
|
|
877
881
|
return withHireGrantAdmission(hqRoot, lane.senior.id, (snapshot) => {
|
|
878
882
|
const current = readLane(hqRoot, lane.lane_id);
|
|
879
883
|
if (grantContext !== undefined &&
|
|
@@ -1054,6 +1058,7 @@ function normalizedLaneListQuery(query) {
|
|
|
1054
1058
|
...(query.active ? { active: true } : {}),
|
|
1055
1059
|
...(query.hierarchy === undefined ? {} : { hierarchy: query.hierarchy }),
|
|
1056
1060
|
...(query.snapshot ? { snapshot: true } : {}),
|
|
1061
|
+
...(query.strictSnapshot ? { strictSnapshot: true } : {}),
|
|
1057
1062
|
};
|
|
1058
1063
|
}
|
|
1059
1064
|
async function readOutpostLaneRecordsUncached(ctx, outpostId, query) {
|
|
@@ -1144,6 +1149,156 @@ async function readOutpostLaneRecords(ctx, outpostId, requestedQuery = {}) {
|
|
|
1144
1149
|
byOutpost.set(outpostId, { queryKey, promise });
|
|
1145
1150
|
return promise;
|
|
1146
1151
|
}
|
|
1152
|
+
/** Refresh only primary-created, non-terminal Outpost mirrors in one bounded owner query per Outpost. */
|
|
1153
|
+
async function refreshRemoteLaneMirrors(ctx, hqRoot, records, parentLaneIds) {
|
|
1154
|
+
const result = {
|
|
1155
|
+
updated: new Map(),
|
|
1156
|
+
unverifiedRemote: new Map(),
|
|
1157
|
+
};
|
|
1158
|
+
const host = reconcileHostFromCtx(ctx);
|
|
1159
|
+
if (host.outpostId !== null || parentLaneIds?.size === 0)
|
|
1160
|
+
return result;
|
|
1161
|
+
let primaryIdentity;
|
|
1162
|
+
const originMatchesThisPrimary = (lane) => {
|
|
1163
|
+
if (lane.origin_host_source !== "imds") {
|
|
1164
|
+
return lane.origin_host === host.hostname;
|
|
1165
|
+
}
|
|
1166
|
+
primaryIdentity ??= ctx.resolveMachineIdentity();
|
|
1167
|
+
if (primaryIdentity.status !== "ok")
|
|
1168
|
+
return false;
|
|
1169
|
+
const primaryImdsId = primaryIdentity.imds ??
|
|
1170
|
+
(primaryIdentity.source === "imds" ? primaryIdentity.id : null);
|
|
1171
|
+
return primaryImdsId !== null && primaryImdsId === lane.origin_host;
|
|
1172
|
+
};
|
|
1173
|
+
const candidates = records
|
|
1174
|
+
.filter((lane) => lane.host.kind === "outpost" &&
|
|
1175
|
+
originMatchesThisPrimary(lane) &&
|
|
1176
|
+
(parentLaneIds === undefined || (lane.senior.kind === "lane" &&
|
|
1177
|
+
lane.senior.id !== undefined &&
|
|
1178
|
+
parentLaneIds.has(lane.senior.id))) &&
|
|
1179
|
+
!isTerminalLaneState(lane.state))
|
|
1180
|
+
.sort((a, b) => a.lane_id.localeCompare(b.lane_id));
|
|
1181
|
+
if (candidates.length === 0)
|
|
1182
|
+
return result;
|
|
1183
|
+
const enabledByCompany = new Map();
|
|
1184
|
+
const enabledCandidates = [];
|
|
1185
|
+
for (const lane of candidates) {
|
|
1186
|
+
const companyKey = JSON.stringify([lane.company?.uid ?? null, lane.company?.slug ?? null]);
|
|
1187
|
+
let enabled = enabledByCompany.get(companyKey);
|
|
1188
|
+
if (enabled === undefined) {
|
|
1189
|
+
enabled = await readLanesRegistryFlag(LANE_REMOTE_MIRROR_SYNC_FLAG, {
|
|
1190
|
+
company: lane.company,
|
|
1191
|
+
env: ctx.env,
|
|
1192
|
+
});
|
|
1193
|
+
enabledByCompany.set(companyKey, enabled);
|
|
1194
|
+
}
|
|
1195
|
+
if (enabled)
|
|
1196
|
+
enabledCandidates.push(lane);
|
|
1197
|
+
}
|
|
1198
|
+
if (enabledCandidates.length === 0)
|
|
1199
|
+
return result;
|
|
1200
|
+
const byOutpost = new Map();
|
|
1201
|
+
for (const lane of enabledCandidates) {
|
|
1202
|
+
const group = byOutpost.get(lane.host.id) ?? [];
|
|
1203
|
+
group.push(lane);
|
|
1204
|
+
byOutpost.set(lane.host.id, group);
|
|
1205
|
+
}
|
|
1206
|
+
for (const [outpostId, group] of byOutpost) {
|
|
1207
|
+
const batch = group.slice(0, REMOTE_MIRROR_REFRESH_BATCH_SIZE);
|
|
1208
|
+
const overflow = group.slice(REMOTE_MIRROR_REFRESH_BATCH_SIZE);
|
|
1209
|
+
if (overflow.length > 0) {
|
|
1210
|
+
const ids = overflow.map((lane) => lane.lane_id);
|
|
1211
|
+
for (const lane of overflow) {
|
|
1212
|
+
result.unverifiedRemote.set(lane.lane_id, "refresh_batch_limit");
|
|
1213
|
+
}
|
|
1214
|
+
process.stderr.write(`hq lanes: unverified remote mirrors from Outpost ${outpostId} remain counted; refresh batch limit left lanes ${ids.join(", ")} unread\n`);
|
|
1215
|
+
}
|
|
1216
|
+
let ownerRecords;
|
|
1217
|
+
try {
|
|
1218
|
+
ownerRecords = await readOutpostLaneRecords(ctx, outpostId, {
|
|
1219
|
+
laneIds: batch.map((lane) => lane.lane_id),
|
|
1220
|
+
snapshot: true,
|
|
1221
|
+
strictSnapshot: true,
|
|
1222
|
+
});
|
|
1223
|
+
}
|
|
1224
|
+
catch (error) {
|
|
1225
|
+
const detail = isLanesError(error)
|
|
1226
|
+
? `${error.error}: ${error.message}`
|
|
1227
|
+
: dispatchErrorMessage(error);
|
|
1228
|
+
for (const lane of batch)
|
|
1229
|
+
result.unverifiedRemote.set(lane.lane_id, detail);
|
|
1230
|
+
process.stderr.write(`hq lanes: unverified remote mirrors from Outpost ${outpostId} remain counted: ${detail}; lanes ${batch.map((lane) => lane.lane_id).join(", ")}\n`);
|
|
1231
|
+
continue;
|
|
1232
|
+
}
|
|
1233
|
+
for (const mirror of batch) {
|
|
1234
|
+
const matches = ownerRecords.filter((lane) => lane.lane_id === mirror.lane_id);
|
|
1235
|
+
const owner = matches[0];
|
|
1236
|
+
const ownerIsHostedByOutpost = owner !== undefined && ((owner.host.kind === "outpost" && owner.host.id === outpostId) ||
|
|
1237
|
+
(owner.host.kind === "local" && owner.host.id.trim() !== ""));
|
|
1238
|
+
if (matches.length !== 1 ||
|
|
1239
|
+
owner === undefined ||
|
|
1240
|
+
!ownerIsHostedByOutpost ||
|
|
1241
|
+
(owner.company?.uid ?? null) !== (mirror.company?.uid ?? null) ||
|
|
1242
|
+
(owner.company?.slug ?? null) !== (mirror.company?.slug ?? null)) {
|
|
1243
|
+
const detail = matches.length === 0
|
|
1244
|
+
? "owner did not return the requested lane"
|
|
1245
|
+
: matches.length > 1
|
|
1246
|
+
? "owner returned duplicate lane rows"
|
|
1247
|
+
: "owner lane identity did not match its mirror";
|
|
1248
|
+
result.unverifiedRemote.set(mirror.lane_id, detail);
|
|
1249
|
+
process.stderr.write(`hq lanes: unverified remote mirror ${mirror.lane_id} from Outpost ${outpostId} remains counted: ${detail}\n`);
|
|
1250
|
+
continue;
|
|
1251
|
+
}
|
|
1252
|
+
const endedAt = isTerminalLaneState(owner.state)
|
|
1253
|
+
? owner.last_envelope?.at ?? owner.updated_at
|
|
1254
|
+
: undefined;
|
|
1255
|
+
const envelopeSummary = isTerminalLaneState(owner.state)
|
|
1256
|
+
? owner.envelope_summary
|
|
1257
|
+
: undefined;
|
|
1258
|
+
const before = readLane(hqRoot, mirror.lane_id);
|
|
1259
|
+
if (before.host.kind !== "outpost" || before.host.id !== outpostId ||
|
|
1260
|
+
!originMatchesThisPrimary(before) || isTerminalLaneState(before.state)) {
|
|
1261
|
+
result.updated.set(mirror.lane_id, before);
|
|
1262
|
+
continue;
|
|
1263
|
+
}
|
|
1264
|
+
const needsUpdate = before.state !== owner.state ||
|
|
1265
|
+
before.queued_reason !== (owner.state === "queued" ? owner.queued_reason : undefined) ||
|
|
1266
|
+
before.ended_at !== endedAt ||
|
|
1267
|
+
before.envelope_summary !== envelopeSummary ||
|
|
1268
|
+
(isTerminalLaneState(owner.state) && (JSON.stringify(before.last_envelope) !== JSON.stringify(owner.last_envelope) ||
|
|
1269
|
+
before.story_complete !== owner.story_complete ||
|
|
1270
|
+
before.story_complete_reason !== owner.story_complete_reason ||
|
|
1271
|
+
before.story_status !== owner.story_status ||
|
|
1272
|
+
before.terminal_reason !== owner.terminal_reason));
|
|
1273
|
+
if (!needsUpdate) {
|
|
1274
|
+
result.updated.set(mirror.lane_id, before);
|
|
1275
|
+
continue;
|
|
1276
|
+
}
|
|
1277
|
+
const updated = retryUpdateLane(hqRoot, mirror.lane_id, "reconcile", (current) => {
|
|
1278
|
+
if (current.host.kind !== "outpost" || current.host.id !== outpostId ||
|
|
1279
|
+
!originMatchesThisPrimary(current) || isTerminalLaneState(current.state))
|
|
1280
|
+
return current;
|
|
1281
|
+
return {
|
|
1282
|
+
...current,
|
|
1283
|
+
state: owner.state,
|
|
1284
|
+
queued_reason: owner.state === "queued" ? owner.queued_reason : undefined,
|
|
1285
|
+
ended_at: endedAt,
|
|
1286
|
+
envelope_summary: envelopeSummary,
|
|
1287
|
+
...(isTerminalLaneState(owner.state) ? {
|
|
1288
|
+
last_envelope: owner.last_envelope,
|
|
1289
|
+
story_complete: owner.story_complete,
|
|
1290
|
+
story_complete_reason: owner.story_complete_reason,
|
|
1291
|
+
story_status: owner.story_status,
|
|
1292
|
+
terminal_reason: owner.terminal_reason,
|
|
1293
|
+
terminal_details: owner.terminal_details,
|
|
1294
|
+
} : {}),
|
|
1295
|
+
};
|
|
1296
|
+
});
|
|
1297
|
+
result.updated.set(mirror.lane_id, updated);
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
return result;
|
|
1301
|
+
}
|
|
1147
1302
|
async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
1148
1303
|
try {
|
|
1149
1304
|
return { lane: readLane(ctx.hqRoot(), laneId), remoteOnly: false };
|
|
@@ -3035,6 +3190,9 @@ async function runCreate(ctx, opts) {
|
|
|
3035
3190
|
: {}),
|
|
3036
3191
|
attribution_history: [],
|
|
3037
3192
|
};
|
|
3193
|
+
if (senior.kind === "lane" && senior.id) {
|
|
3194
|
+
await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot), new Set([senior.id]));
|
|
3195
|
+
}
|
|
3038
3196
|
const lane = senior.kind === "lane"
|
|
3039
3197
|
? withHireGrantAdmission(hqRoot, senior.id, ({ parent, children, parentGrant, availableChildGrant }) => {
|
|
3040
3198
|
if (isTerminalLaneState(parent.state)) {
|
|
@@ -4228,7 +4386,7 @@ async function runList(ctx, opts) {
|
|
|
4228
4386
|
const hierarchyIds = opts.hierarchy === undefined
|
|
4229
4387
|
? undefined
|
|
4230
4388
|
: new Set(selectOutpostHireGrantHierarchy(opts.hierarchy, laneSource).map((lane) => lane.lane_id));
|
|
4231
|
-
|
|
4389
|
+
let selectedLanes = laneSource
|
|
4232
4390
|
.filter((lane) => {
|
|
4233
4391
|
if (requestedLaneIds.length > 0 && !requestedIds.has(lane.lane_id))
|
|
4234
4392
|
return false;
|
|
@@ -4254,9 +4412,27 @@ async function runList(ctx, opts) {
|
|
|
4254
4412
|
})
|
|
4255
4413
|
.sort(compareLaneQueueOrder);
|
|
4256
4414
|
if (opts.snapshot) {
|
|
4257
|
-
printJson({
|
|
4415
|
+
printJson({
|
|
4416
|
+
ok: true,
|
|
4417
|
+
action: "list",
|
|
4418
|
+
lanes: selectedLanes.map((lane) => {
|
|
4419
|
+
if (!isTerminalLaneState(lane.state))
|
|
4420
|
+
return lane;
|
|
4421
|
+
const envelope = readLatestEnvelopeRecord(hqRoot, lane.lane_id)?.envelope;
|
|
4422
|
+
return envelope?.summary ? { ...lane, envelope_summary: envelope.summary } : lane;
|
|
4423
|
+
}),
|
|
4424
|
+
});
|
|
4258
4425
|
return;
|
|
4259
4426
|
}
|
|
4427
|
+
const remoteRefresh = await refreshRemoteLaneMirrors(ctx, hqRoot, laneSource, new Set(selectedLanes
|
|
4428
|
+
.filter((lane) => !isTerminalLaneState(lane.state))
|
|
4429
|
+
.map((lane) => lane.lane_id)));
|
|
4430
|
+
for (const [laneId, updated] of remoteRefresh.updated) {
|
|
4431
|
+
const index = laneSource.findIndex((lane) => lane.lane_id === laneId);
|
|
4432
|
+
if (index >= 0)
|
|
4433
|
+
laneSource[index] = updated;
|
|
4434
|
+
}
|
|
4435
|
+
selectedLanes = selectedLanes.map((lane) => remoteRefresh.updated.get(lane.lane_id) ?? lane);
|
|
4260
4436
|
const now = ctx.now();
|
|
4261
4437
|
const host = reconcileHostFromCtx(ctx);
|
|
4262
4438
|
const allLanes = laneSource;
|
|
@@ -4313,6 +4489,7 @@ async function runList(ctx, opts) {
|
|
|
4313
4489
|
return {
|
|
4314
4490
|
...lane,
|
|
4315
4491
|
...hireByLane.get(lane.lane_id),
|
|
4492
|
+
...(remoteRefresh.unverifiedRemote.has(lane.lane_id) ? { unverified_remote: true } : {}),
|
|
4316
4493
|
stories: laneStories(lane),
|
|
4317
4494
|
story_ids: laneStories(lane).map((story) => story.story_id),
|
|
4318
4495
|
no_story_reason: lane.no_story_reason ?? null,
|
|
@@ -4349,6 +4526,9 @@ async function runList(ctx, opts) {
|
|
|
4349
4526
|
ok: true,
|
|
4350
4527
|
action: "list",
|
|
4351
4528
|
lanes,
|
|
4529
|
+
...(remoteRefresh.unverifiedRemote.size > 0
|
|
4530
|
+
? { unverified_remote_lanes: [...remoteRefresh.unverifiedRemote.keys()] }
|
|
4531
|
+
: {}),
|
|
4352
4532
|
...(host_capacity !== undefined ? { host_capacity } : {}),
|
|
4353
4533
|
};
|
|
4354
4534
|
if (opts.human)
|
|
@@ -4768,6 +4948,13 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
|
|
|
4768
4948
|
? [initial.senior.id]
|
|
4769
4949
|
: []),
|
|
4770
4950
|
];
|
|
4951
|
+
if (authorization === "senior") {
|
|
4952
|
+
const parentLaneIds = new Set([initial.lane_id]);
|
|
4953
|
+
if (initial.senior.kind === "lane" && initial.senior.id) {
|
|
4954
|
+
parentLaneIds.add(initial.senior.id);
|
|
4955
|
+
}
|
|
4956
|
+
await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot), parentLaneIds);
|
|
4957
|
+
}
|
|
4771
4958
|
const updated = ctx.withGrantHireLocks(hqRoot, lockedLaneIds, () => {
|
|
4772
4959
|
const current = readLane(hqRoot, laneId);
|
|
4773
4960
|
const currentCallerAuthorization = authorization === "senior"
|
|
@@ -6221,6 +6408,12 @@ async function reparentOnOwner(ctx, laneId, opts) {
|
|
|
6221
6408
|
const nextSenior = parseSeniorRef(opts.senior);
|
|
6222
6409
|
assertSeniorExists(hqRoot, nextSenior, ctx);
|
|
6223
6410
|
const initial = readLane(hqRoot, laneId);
|
|
6411
|
+
const refreshParents = new Set();
|
|
6412
|
+
if (initial.senior.kind === "lane" && initial.senior.id)
|
|
6413
|
+
refreshParents.add(initial.senior.id);
|
|
6414
|
+
if (nextSenior.kind === "lane" && nextSenior.id)
|
|
6415
|
+
refreshParents.add(nextSenior.id);
|
|
6416
|
+
await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot), refreshParents);
|
|
6224
6417
|
const lockIds = [
|
|
6225
6418
|
laneId,
|
|
6226
6419
|
...(initial.senior.kind === "lane" && initial.senior.id ? [initial.senior.id] : []),
|
|
@@ -6380,12 +6573,58 @@ const CANONICAL_LOG_STREAMS = [
|
|
|
6380
6573
|
];
|
|
6381
6574
|
async function runLogs(ctx, laneId, opts) {
|
|
6382
6575
|
const hqRoot = ctx.hqRoot();
|
|
6383
|
-
readLane(hqRoot, laneId);
|
|
6384
|
-
const logsDir = path.join(runDir(hqRoot, laneId), "logs");
|
|
6576
|
+
const lane = readLane(hqRoot, laneId);
|
|
6385
6577
|
const tail = parseOptionalInt(opts.tail, "--tail");
|
|
6386
6578
|
if (tail === 0) {
|
|
6387
6579
|
throw new LanesError("invalid_integer", "--tail must be a positive integer", { flag: "--tail", value: opts.tail });
|
|
6388
6580
|
}
|
|
6581
|
+
if (lane.host.kind === "outpost" &&
|
|
6582
|
+
!laneRunsHere(lane, reconcileHostFromCtx(ctx)) &&
|
|
6583
|
+
await readLanesRegistryFlag(LANE_REMOTE_MIRROR_SYNC_FLAG, {
|
|
6584
|
+
company: lane.company,
|
|
6585
|
+
env: ctx.env,
|
|
6586
|
+
})) {
|
|
6587
|
+
const outpostId = lane.host.id;
|
|
6588
|
+
const waitMs = outpostSingleLaneWaitMs(ctx.env);
|
|
6589
|
+
let dispatched;
|
|
6590
|
+
try {
|
|
6591
|
+
dispatched = await ctx.dispatchOutpostExec({
|
|
6592
|
+
outpostId,
|
|
6593
|
+
command: buildOutpostLaneCommand({
|
|
6594
|
+
action: "logs",
|
|
6595
|
+
laneId,
|
|
6596
|
+
hqBinary: outpostCliOverride(ctx, outpostId),
|
|
6597
|
+
...(tail === undefined ? {} : { tail: String(tail) }),
|
|
6598
|
+
json: true,
|
|
6599
|
+
}),
|
|
6600
|
+
timeoutSeconds: Math.ceil(waitMs / 1000),
|
|
6601
|
+
waitMs,
|
|
6602
|
+
});
|
|
6603
|
+
}
|
|
6604
|
+
catch (error) {
|
|
6605
|
+
throw new LanesError("outpost_lane_logs_unavailable", `Could not read logs for lane ${laneId} from Outpost ${outpostId}: ${dispatchErrorMessage(error)}`, { lane_id: laneId, outpost_id: outpostId, wait_ms: waitMs, wait_env: OUTPOST_SINGLE_LANE_WAIT_ENV });
|
|
6606
|
+
}
|
|
6607
|
+
if (dispatched.done !== true || dispatched.status !== "Success" || dispatched.exitCode !== 0) {
|
|
6608
|
+
throw new LanesError("outpost_lane_logs_unavailable", `Outpost ${outpostId} did not return logs for lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId, status: dispatched.status, exit_code: dispatched.exitCode });
|
|
6609
|
+
}
|
|
6610
|
+
let body;
|
|
6611
|
+
try {
|
|
6612
|
+
body = JSON.parse(dispatched.stdout);
|
|
6613
|
+
}
|
|
6614
|
+
catch (error) {
|
|
6615
|
+
throw new LanesError("outpost_lane_logs_invalid", `Outpost ${outpostId} returned invalid logs JSON for lane ${laneId}: ${dispatchErrorMessage(error)}`, { lane_id: laneId, outpost_id: outpostId });
|
|
6616
|
+
}
|
|
6617
|
+
if (body === null || typeof body !== "object" || Array.isArray(body) ||
|
|
6618
|
+
body.ok !== true ||
|
|
6619
|
+
body.action !== "logs" ||
|
|
6620
|
+
body.lane_id !== laneId ||
|
|
6621
|
+
!Array.isArray(body.streams)) {
|
|
6622
|
+
throw new LanesError("outpost_lane_logs_invalid", `Outpost ${outpostId} returned a malformed logs payload for lane ${laneId}`, { lane_id: laneId, outpost_id: outpostId });
|
|
6623
|
+
}
|
|
6624
|
+
printJson(body);
|
|
6625
|
+
return;
|
|
6626
|
+
}
|
|
6627
|
+
const logsDir = path.join(runDir(hqRoot, laneId), "logs");
|
|
6389
6628
|
const names = new Set(CANONICAL_LOG_STREAMS);
|
|
6390
6629
|
if (fs.existsSync(logsDir)) {
|
|
6391
6630
|
for (const name of fs.readdirSync(logsDir)) {
|
|
@@ -7059,6 +7298,9 @@ function writeAnswerDeliveryFailed(hqRoot, laneId, questionId, err, at) {
|
|
|
7059
7298
|
async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
7060
7299
|
const fromEnv = ctx.env.HQ_ROOT?.trim();
|
|
7061
7300
|
const hqRoot = fromEnv && ctx.existsSync(fromEnv) ? fromEnv : ctx.hqRoot();
|
|
7301
|
+
const mirrorRefresh = opts.dryRun
|
|
7302
|
+
? { updated: new Map(), unverifiedRemote: new Map() }
|
|
7303
|
+
: await refreshRemoteLaneMirrors(ctx, hqRoot, loadAllLanes(hqRoot));
|
|
7062
7304
|
const summary = await runReconcilePass(hqRoot, {
|
|
7063
7305
|
now: ctx.now,
|
|
7064
7306
|
host: reconcileHostFromCtx(ctx),
|
|
@@ -7174,7 +7416,12 @@ async function runReconcile(ctx, opts, recoverQueuedMessages) {
|
|
|
7174
7416
|
}
|
|
7175
7417
|
},
|
|
7176
7418
|
});
|
|
7177
|
-
printJson(
|
|
7419
|
+
printJson({
|
|
7420
|
+
...summary,
|
|
7421
|
+
...(mirrorRefresh.unverifiedRemote.size > 0
|
|
7422
|
+
? { unverified_remote_lanes: [...mirrorRefresh.unverifiedRemote.keys()] }
|
|
7423
|
+
: {}),
|
|
7424
|
+
});
|
|
7178
7425
|
if (!summary.ok) {
|
|
7179
7426
|
process.exit(1);
|
|
7180
7427
|
}
|
|
@@ -27,7 +27,7 @@ export interface OutpostDispatchResult {
|
|
|
27
27
|
stderr: string;
|
|
28
28
|
}
|
|
29
29
|
export interface OutpostLaneCommand {
|
|
30
|
-
action: "message" | "resume" | "stop" | "show" | "grant" | "grant-target" | "reparent-target" | "approve-target";
|
|
30
|
+
action: "message" | "resume" | "stop" | "show" | "logs" | "grant" | "grant-target" | "reparent-target" | "approve-target";
|
|
31
31
|
laneId: string;
|
|
32
32
|
hqBinary?: string;
|
|
33
33
|
text?: string;
|
|
@@ -47,6 +47,8 @@ export interface OutpostLaneCommand {
|
|
|
47
47
|
senior?: string;
|
|
48
48
|
reason?: string;
|
|
49
49
|
compact?: boolean;
|
|
50
|
+
tail?: string;
|
|
51
|
+
json?: boolean;
|
|
50
52
|
}
|
|
51
53
|
export declare function buildOutpostQuestionAnswerCommand(input: {
|
|
52
54
|
questionId: string;
|
|
@@ -174,6 +174,10 @@ export function buildOutpostLaneCommand(input) {
|
|
|
174
174
|
args.push("--keep-alive");
|
|
175
175
|
if (input.compact === false)
|
|
176
176
|
args.push("--no-compact");
|
|
177
|
+
if (input.action === "logs" && input.tail !== undefined)
|
|
178
|
+
args.push("--tail", input.tail);
|
|
179
|
+
if (input.action === "logs" && input.json === true)
|
|
180
|
+
args.push("--json");
|
|
177
181
|
return withRemoteHqDir(joinCommandParts(args));
|
|
178
182
|
}
|
|
179
183
|
/** True when a remote CLI exited after rejecting the forwarded effort flag. */
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -416,6 +416,17 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
416
416
|
requireInt(raw.version, "version");
|
|
417
417
|
requireInt(raw.depth, "depth");
|
|
418
418
|
requireInt(raw.priority, "priority");
|
|
419
|
+
if (raw.ended_at !== undefined) {
|
|
420
|
+
requireTimestamp(raw.ended_at, "ended_at", filePath);
|
|
421
|
+
}
|
|
422
|
+
if (raw.envelope_summary !== undefined &&
|
|
423
|
+
(typeof raw.envelope_summary !== "string" || raw.envelope_summary.trim() === "")) {
|
|
424
|
+
throw new LaneSchemaError("envelope_summary must be a non-empty string when present", {
|
|
425
|
+
path: filePath,
|
|
426
|
+
field: "envelope_summary",
|
|
427
|
+
value: raw.envelope_summary,
|
|
428
|
+
});
|
|
429
|
+
}
|
|
419
430
|
if (raw.hire_grant !== undefined &&
|
|
420
431
|
(typeof raw.hire_grant !== "number" || !Number.isSafeInteger(raw.hire_grant) || raw.hire_grant < 0)) {
|
|
421
432
|
throw new LaneSchemaError("Expected non-negative safe integer for hire_grant", {
|
|
@@ -526,6 +526,10 @@ export interface LaneRecord {
|
|
|
526
526
|
version: number;
|
|
527
527
|
created_at: string;
|
|
528
528
|
updated_at: string;
|
|
529
|
+
/** Owner-host completion time copied to a primary mirror during reconcile. */
|
|
530
|
+
ended_at?: string;
|
|
531
|
+
/** Summary from the owner's latest terminal envelope, copied to a mirror. */
|
|
532
|
+
envelope_summary?: string;
|
|
529
533
|
attribution_history: AttributionHistoryEntry[];
|
|
530
534
|
last_envelope?: LastEnvelopeRef;
|
|
531
535
|
/** Effective model id recorded at create/spawn. Omitted when the provider chooses. */
|
package/dist/lib/lanes/types.js
CHANGED
|
@@ -226,7 +226,7 @@ export const LANE_FIELD_WRITERS = {
|
|
|
226
226
|
priority: ["command"], worker: ["creator", "command"], worker_path: ["creator", "command"], worker_pack: ["creator", "command"], worker_profile: ["creator", "command"], worker_profile_hash: ["creator", "command", "spawn"], registry_worker: ["creator", "command", "spawn"], registry_worker_approval_history: ["command", "watcher", "reconcile"], worker_profile_repin_history: ["command"], worker_profile_ignored: ["creator", "command", "spawn"], worker_policy_delivery: ["command", "spawn"],
|
|
227
227
|
keep_alive: ["creator", "command"], keep_alive_standing: ["command"], keep_alive_opted_in_at: ["creator", "command"], keep_alive_blocked_reason: ["command", "reconcile"], keep_alive_resume_count: ["command", "reconcile"], keep_alive_last_resume_at: ["command", "reconcile"], keep_alive_next_eligible_at: ["command", "reconcile"], keep_alive_exhausted_at: ["command", "reconcile"],
|
|
228
228
|
provider: ["creator"], account: ["creator", "spawn", "command", "watcher", "reconcile"], account_selection: ["creator", "command"], admission_accounts: ["creator", "command"], account_history: ["command", "spawn", "watcher", "reconcile"], failed_resume_history: ["command"], pending_resume: ["command", "spawn", "watcher", "reconcile"], placement_default: ["creator"], host: ["creator", "command"], origin_host: ["creator"], origin_host_source: ["creator"],
|
|
229
|
-
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], attribution_history: ["command"],
|
|
229
|
+
version: ["creator"], created_at: ["creator"], updated_at: ["creator"], ended_at: ["reconcile"], envelope_summary: ["reconcile"], attribution_history: ["command"],
|
|
230
230
|
last_envelope: ["watcher", "reconcile"], model: ["creator", "spawn", "command"], model_source: ["creator", "spawn", "command"], effort: ["creator", "spawn", "command"], effort_source: ["creator", "spawn", "command"], fast_mode: ["spawn"],
|
|
231
231
|
last_message_at: ["command"], status_gate: ["spawn", "watcher"], status_gate_history: ["watcher"], blocker_freshness_state: ["watcher"], kr_freshness_state: ["watcher"], last_compaction: ["spawn"], story_complete: ["watcher", "reconcile"], story_complete_reason: ["watcher", "reconcile"], story_status: ["watcher", "reconcile"],
|
|
232
232
|
overdue: ["reconcile"], overdue_at: ["reconcile"], last_host_report_at: ["watcher", "spawn", "reconcile"], stalled: ["watcher"], stalled_at: ["watcher"], stalled_reason: ["watcher"],
|