@indigoai-us/hq-cli 5.226.0 → 5.227.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 +15 -0
- package/dist/command-catalog.generated.d.ts +6 -0
- package/dist/command-catalog.generated.js +8 -0
- package/dist/commands/agents.js +2 -1
- package/dist/commands/files-browse.js +7 -4
- package/dist/commands/files.js +13 -8
- package/dist/commands/groups.js +6 -5
- package/dist/commands/lanes-checks.d.ts +2 -0
- package/dist/commands/lanes-checks.js +13 -11
- package/dist/commands/lanes-import.d.ts +1 -0
- package/dist/commands/lanes-import.js +12 -11
- package/dist/commands/lanes-report.js +21 -14
- package/dist/commands/lanes-tracker.d.ts +1 -0
- package/dist/commands/lanes-tracker.js +39 -31
- package/dist/commands/lanes.d.ts +3 -0
- package/dist/commands/lanes.js +320 -2
- package/dist/lib/checkpoint-ontology/plan.d.ts +4 -6
- package/dist/lib/checkpoint-ontology/plan.js +8 -26
- package/dist/lib/lanes/reconcile.d.ts +2 -0
- package/dist/lib/lanes/reconcile.js +16 -0
- package/dist/lib/lanes/store.js +9 -1
- package/dist/lib/lanes/tracker-mesh-questions.d.ts +54 -0
- package/dist/lib/lanes/tracker-mesh-questions.js +138 -0
- package/dist/lib/lanes/tracker-mesh.d.ts +19 -0
- package/dist/lib/lanes/tracker-mesh.js +228 -0
- package/dist/lib/lanes/tracker-store.d.ts +13 -2
- package/dist/lib/lanes/tracker-store.js +1 -1
- package/dist/lib/lanes/types.d.ts +4 -0
- package/dist/utils/table-width.d.ts +7 -0
- package/dist/utils/table-width.js +15 -0
- package/package.json +1 -1
package/dist/commands/lanes.js
CHANGED
|
@@ -17,6 +17,8 @@ import { registerLaneChecksCommand } from "./lanes-checks.js";
|
|
|
17
17
|
import { registerLaneReportCommands } from "./lanes-report.js";
|
|
18
18
|
import { registerLaneTrackerImportCommand } from "./lanes-import.js";
|
|
19
19
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
20
|
+
import { createTrackerMeshQuestionClient, deliverTrackerMeshQuestionOnce } from "../lib/lanes/tracker-mesh-questions.js";
|
|
21
|
+
import { createWorkMeshTrackerStore } from "../lib/lanes/tracker-mesh.js";
|
|
20
22
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
21
23
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
22
24
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
@@ -44,7 +46,7 @@ import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_
|
|
|
44
46
|
import { activeLaneStories, laneStories, laneStory, storyWarning, } from "../lib/lanes/stories.js";
|
|
45
47
|
import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
|
|
46
48
|
import { keepAliveExclusion, LEAD_LANE_KEEP_ALIVE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
|
|
47
|
-
import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
|
|
49
|
+
import { questionBodyAbsPath, questionRecordRefusalCode, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
|
|
48
50
|
import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
|
|
49
51
|
import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
|
|
50
52
|
import { assertUniqueAccountHomes, providerCompactAfterMs } from "../lib/lanes/config.js";
|
|
@@ -327,6 +329,7 @@ function resolveDeps(deps = {}) {
|
|
|
327
329
|
((company) => defaultLaneOutpostQuestionVisibilityEnabled(company, env)),
|
|
328
330
|
laneTrackerEnabled: deps.laneTrackerEnabled ?? defaultLaneTrackerEnabled,
|
|
329
331
|
laneTrackerMeshEnabled: deps.laneTrackerMeshEnabled ?? defaultLaneTrackerMeshEnabled,
|
|
332
|
+
trackerMeshRequest: deps.trackerMeshRequest,
|
|
330
333
|
laneHostScopedAccountHomesEnabled: deps.laneHostScopedAccountHomesEnabled ??
|
|
331
334
|
((company) => defaultLaneHostScopedAccountHomesEnabled(company, env)),
|
|
332
335
|
trackerActor: deps.trackerActor ?? defaultTrackerActor,
|
|
@@ -516,6 +519,167 @@ function requireQuestionBody(hqRoot, question) {
|
|
|
516
519
|
}
|
|
517
520
|
return body;
|
|
518
521
|
}
|
|
522
|
+
function trackerMeshAudience(ref) {
|
|
523
|
+
if (ref.kind === "user")
|
|
524
|
+
return "owner";
|
|
525
|
+
if (ref.kind === "session")
|
|
526
|
+
return "operator";
|
|
527
|
+
return `lane:${ref.id ?? ""}`;
|
|
528
|
+
}
|
|
529
|
+
function trackerMeshAudienceKind(value) {
|
|
530
|
+
if (value === "owner" || value.startsWith("owner:"))
|
|
531
|
+
return "owner";
|
|
532
|
+
if (value === "operator" || value.startsWith("operator:"))
|
|
533
|
+
return "operator";
|
|
534
|
+
return "lane";
|
|
535
|
+
}
|
|
536
|
+
function trackerMeshQuestionInput(lane, question, body) {
|
|
537
|
+
if (!lane.project_id.trim())
|
|
538
|
+
throw new LanesError("tracker_mesh_question_invalid", "Question lane has no project id", { lane_id: lane.lane_id });
|
|
539
|
+
const route = question.routing_history;
|
|
540
|
+
const currentAudience = route.at(-1)?.audience ?? lane.senior;
|
|
541
|
+
return {
|
|
542
|
+
laneId: lane.lane_id,
|
|
543
|
+
...(lane.thread_id ? { threadId: lane.thread_id } : {}),
|
|
544
|
+
projectId: lane.project_id,
|
|
545
|
+
audience: trackerMeshAudienceKind(trackerMeshAudience(currentAudience)),
|
|
546
|
+
audienceChain: route.length ? route.map((entry) => trackerMeshAudience(entry.audience)) : [trackerMeshAudience(currentAudience)],
|
|
547
|
+
routingHistory: route.map((entry) => ({ audience: trackerMeshAudience(entry.audience), at: entry.at, reason: entry.reason })),
|
|
548
|
+
question: body.question,
|
|
549
|
+
context: body.context,
|
|
550
|
+
options: body.options,
|
|
551
|
+
...(body.recommended !== undefined ? { recommended: body.recommended } : {}),
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
function assertMeshQuestionMatchesLocal(row, question, company) {
|
|
555
|
+
if (row.companyUid !== company.uid || row.laneId !== question.lane_id || row.projectId !== question.project_id) {
|
|
556
|
+
throw new LanesError("tracker_question_company_mismatch", "Work Mesh question does not match the local lane company and project", {
|
|
557
|
+
question_id: question.question_id,
|
|
558
|
+
mesh_question_id: row.questionId,
|
|
559
|
+
lane_id: question.lane_id,
|
|
560
|
+
});
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
async function indexTrackerQuestion(ctx, hqRoot, lane, question, body, company, client = createTrackerMeshQuestionClient({ company, request: ctx.trackerMeshRequest })) {
|
|
564
|
+
if (question.mesh_question_id)
|
|
565
|
+
return question;
|
|
566
|
+
if (question.status !== "pending")
|
|
567
|
+
return question;
|
|
568
|
+
const input = trackerMeshQuestionInput(lane, question, body);
|
|
569
|
+
const candidates = (await client.list(["pending", "answered"])).filter((row) => row.laneId === input.laneId && row.projectId === input.projectId && row.threadId === (input.threadId ?? row.threadId) &&
|
|
570
|
+
row.audience === input.audience && row.question === input.question && row.context === input.context &&
|
|
571
|
+
JSON.stringify(row.options) === JSON.stringify(input.options) &&
|
|
572
|
+
row.routingHistory.at(-1)?.at === input.routingHistory.at(-1)?.at);
|
|
573
|
+
if (candidates.length > 1) {
|
|
574
|
+
throw new LanesError("tracker_mesh_question_ambiguous", "More than one pending Work Mesh question matches this local question", { question_id: question.question_id, lane_id: lane.lane_id });
|
|
575
|
+
}
|
|
576
|
+
const row = candidates[0] ?? await client.create(input);
|
|
577
|
+
assertMeshQuestionMatchesLocal(row, question, company);
|
|
578
|
+
return casUpdateQuestion(hqRoot, question.question_id, (current) => {
|
|
579
|
+
if (current.status !== "pending")
|
|
580
|
+
return current;
|
|
581
|
+
if (current.mesh_question_id && current.mesh_question_id !== row.questionId) {
|
|
582
|
+
throw new LanesError("tracker_mesh_question_conflict", "Local question was already linked to another Work Mesh question", { question_id: current.question_id });
|
|
583
|
+
}
|
|
584
|
+
return current.mesh_question_id === row.questionId ? current : { ...current, mesh_question_id: row.questionId };
|
|
585
|
+
});
|
|
586
|
+
}
|
|
587
|
+
async function syncTrackerMeshQuestions(ctx, hqRoot) {
|
|
588
|
+
if (!ctx.laneTrackerMeshEnabled)
|
|
589
|
+
return;
|
|
590
|
+
const lanes = new Map(loadAllLanes(hqRoot).map((lane) => [lane.lane_id, lane]));
|
|
591
|
+
const byCompany = new Map();
|
|
592
|
+
for (const lane of lanes.values()) {
|
|
593
|
+
let company;
|
|
594
|
+
try {
|
|
595
|
+
company = await resolveQuestionCompanyWithFallback(hqRoot, lane, () => resolveActiveQuestionCompany(ctx, "Cannot sync lane questions without an HQ company"));
|
|
596
|
+
}
|
|
597
|
+
catch (err) {
|
|
598
|
+
if (lane.company || lane.mesh_company ||
|
|
599
|
+
!isLanesError(err) || err.error !== "question_company_unavailable" ||
|
|
600
|
+
err.details.resolution !== "active_company_required")
|
|
601
|
+
throw err;
|
|
602
|
+
continue;
|
|
603
|
+
}
|
|
604
|
+
if (!await ctx.laneTrackerMeshEnabled(company))
|
|
605
|
+
continue;
|
|
606
|
+
const key = `${company.uid}\0${company.slug}`;
|
|
607
|
+
if (!byCompany.has(key))
|
|
608
|
+
byCompany.set(key, { company, client: createTrackerMeshQuestionClient({ company, request: ctx.trackerMeshRequest }) });
|
|
609
|
+
}
|
|
610
|
+
const localQuestions = loadAllQuestions(hqRoot);
|
|
611
|
+
for (const question of localQuestions) {
|
|
612
|
+
if (question.status !== "pending" || question.mesh_question_id)
|
|
613
|
+
continue;
|
|
614
|
+
const lane = lanes.get(question.lane_id);
|
|
615
|
+
if (!lane)
|
|
616
|
+
continue;
|
|
617
|
+
const company = question.company ?? lane.company ?? lane.mesh_company;
|
|
618
|
+
if (!company)
|
|
619
|
+
continue;
|
|
620
|
+
const client = byCompany.get(`${company.uid}\0${company.slug}`)?.client;
|
|
621
|
+
if (!client)
|
|
622
|
+
continue;
|
|
623
|
+
const body = requireQuestionBody(hqRoot, question);
|
|
624
|
+
await indexTrackerQuestion(ctx, hqRoot, lane, question, body, company, client);
|
|
625
|
+
}
|
|
626
|
+
for (const { company, client } of byCompany.values()) {
|
|
627
|
+
const remote = await client.list(["answered"]);
|
|
628
|
+
const remoteById = new Map(remote.map((row) => [row.questionId, row]));
|
|
629
|
+
for (const question of loadAllQuestions(hqRoot)) {
|
|
630
|
+
if (!question.mesh_question_id || question.company?.uid !== company.uid)
|
|
631
|
+
continue;
|
|
632
|
+
const row = remoteById.get(question.mesh_question_id);
|
|
633
|
+
if (!row)
|
|
634
|
+
continue;
|
|
635
|
+
assertMeshQuestionMatchesLocal(row, question, company);
|
|
636
|
+
const lane = lanes.get(question.lane_id);
|
|
637
|
+
if (!lane)
|
|
638
|
+
continue;
|
|
639
|
+
if (row.status === "answered" && question.status === "pending") {
|
|
640
|
+
if (typeof row.choice !== "string" || typeof row.answeredAt !== "string" || typeof row.answeredBy !== "string") {
|
|
641
|
+
throw new LanesError("tracker_mesh_response_invalid", "Answered Work Mesh question is missing its answer fields", { mesh_question_id: row.questionId });
|
|
642
|
+
}
|
|
643
|
+
const currentBody = requireQuestionBody(hqRoot, question);
|
|
644
|
+
let notesRef;
|
|
645
|
+
let nextBody = question.body;
|
|
646
|
+
if (row.notes !== undefined) {
|
|
647
|
+
const bodyQuestion = question.company || question.body.store === "personal" ? question : { ...question, company };
|
|
648
|
+
const stored = writeQuestionBody(hqRoot, bodyQuestion, { ...currentBody, notes: row.notes });
|
|
649
|
+
notesRef = stored.ref;
|
|
650
|
+
nextBody = stored;
|
|
651
|
+
}
|
|
652
|
+
casUpdateQuestion(hqRoot, question.question_id, (current) => {
|
|
653
|
+
if (current.status !== "pending")
|
|
654
|
+
return current;
|
|
655
|
+
if (current.mesh_question_id !== row.questionId)
|
|
656
|
+
throw new LanesError("tracker_mesh_question_conflict", "Local question link changed during answer sync", { question_id: current.question_id });
|
|
657
|
+
return {
|
|
658
|
+
...current,
|
|
659
|
+
company: current.company ?? company,
|
|
660
|
+
body: nextBody,
|
|
661
|
+
status: "answered",
|
|
662
|
+
answer: {
|
|
663
|
+
choice: row.choice,
|
|
664
|
+
answer_kind: currentBody.options.includes(row.choice) ? "option" : "freeform",
|
|
665
|
+
notes_ref: notesRef,
|
|
666
|
+
answered_by: row.answeredBy,
|
|
667
|
+
answered_at: row.answeredAt,
|
|
668
|
+
answered_level: current.hop,
|
|
669
|
+
},
|
|
670
|
+
};
|
|
671
|
+
});
|
|
672
|
+
}
|
|
673
|
+
}
|
|
674
|
+
for (const question of loadAllQuestions(hqRoot)) {
|
|
675
|
+
if (question.company?.uid !== company.uid || !question.mesh_question_id)
|
|
676
|
+
continue;
|
|
677
|
+
await deliverTrackerMeshQuestionOnce(question, company, client, (deliveredAt) => casUpdateQuestion(hqRoot, question.question_id, (current) => current.mesh_delivered_at
|
|
678
|
+
? current
|
|
679
|
+
: { ...current, mesh_delivered_at: deliveredAt }));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
}
|
|
519
683
|
function loadAllLanes(hqRoot) {
|
|
520
684
|
return listLaneIds(hqRoot).map((id) => readLane(hqRoot, id));
|
|
521
685
|
}
|
|
@@ -7396,7 +7560,7 @@ async function runAsk(ctx, opts) {
|
|
|
7396
7560
|
const attempts = ASK_ATTEMPTS;
|
|
7397
7561
|
for (let i = 0; i < attempts; i += 1) {
|
|
7398
7562
|
try {
|
|
7399
|
-
|
|
7563
|
+
let withBody = raiseLaneQuestion({
|
|
7400
7564
|
hqRoot,
|
|
7401
7565
|
lane,
|
|
7402
7566
|
body,
|
|
@@ -7406,6 +7570,9 @@ async function runAsk(ctx, opts) {
|
|
|
7406
7570
|
questionId,
|
|
7407
7571
|
});
|
|
7408
7572
|
questionId = withBody.question_id;
|
|
7573
|
+
if (await ctx.laneTrackerMeshEnabled(questionCompany)) {
|
|
7574
|
+
withBody = await indexTrackerQuestion(ctx, hqRoot, lane, withBody, body, questionCompany);
|
|
7575
|
+
}
|
|
7409
7576
|
await emitLaneMeshEvent(ctx, lane.mesh_company ?? lane.company, lane.thread_id, "lane_question", {
|
|
7410
7577
|
lane_id: lane.lane_id,
|
|
7411
7578
|
question_id: withBody.question_id,
|
|
@@ -7792,18 +7959,28 @@ async function runQuestionsList(ctx, opts) {
|
|
|
7792
7959
|
const outpostLoad = opts.allHosts
|
|
7793
7960
|
? await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot, company)
|
|
7794
7961
|
: { questions: [], errors: [] };
|
|
7962
|
+
const meshRows = await ctx.laneTrackerMeshEnabled(company) && status !== "expired"
|
|
7963
|
+
? await createTrackerMeshQuestionClient({ company, request: ctx.trackerMeshRequest }).list(status ? [status] : undefined)
|
|
7964
|
+
: [];
|
|
7795
7965
|
for (const owner of outpostLoad.questions) {
|
|
7796
7966
|
assertQuestionCompanyAccess(owner.question, owner.lane, company, hqRoot);
|
|
7797
7967
|
syncPrimaryQuestionDelivery(hqRoot, owner);
|
|
7798
7968
|
}
|
|
7799
7969
|
const remoteQuestionIds = new Set(outpostLoad.questions.map(({ question }) => question.question_id));
|
|
7970
|
+
const localMeshQuestionIds = new Set(localQuestions.map((question) => question.mesh_question_id).filter((id) => Boolean(id)));
|
|
7800
7971
|
const questions = [
|
|
7801
7972
|
...localQuestions
|
|
7802
7973
|
.filter((q) => !remoteQuestionIds.has(q.question_id))
|
|
7803
7974
|
.map((q) => {
|
|
7975
|
+
const meshRow = q.mesh_question_id ? meshRows.find((row) => row.questionId === q.mesh_question_id) : undefined;
|
|
7804
7976
|
const row = {
|
|
7805
7977
|
...q,
|
|
7806
7978
|
audience: resolveAudience(hqRoot, q, ctx),
|
|
7979
|
+
...(meshRow ? {
|
|
7980
|
+
mesh_question_id: meshRow.questionId,
|
|
7981
|
+
body_preview: { question: meshRow.question, context: meshRow.context ?? "", options: meshRow.options, ...(meshRow.recommended ? { recommended: meshRow.recommended } : {}) },
|
|
7982
|
+
host: `${meshRow.host.kind}:${meshRow.host.id}`,
|
|
7983
|
+
} : {}),
|
|
7807
7984
|
...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
|
|
7808
7985
|
};
|
|
7809
7986
|
if (!opts.allHosts)
|
|
@@ -7835,6 +8012,50 @@ async function runQuestionsList(ctx, opts) {
|
|
|
7835
8012
|
audience,
|
|
7836
8013
|
...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
|
|
7837
8014
|
})),
|
|
8015
|
+
...meshRows
|
|
8016
|
+
.filter((row) => {
|
|
8017
|
+
if (localMeshQuestionIds.has(row.questionId))
|
|
8018
|
+
return false;
|
|
8019
|
+
if (status && row.status !== status)
|
|
8020
|
+
return false;
|
|
8021
|
+
if (opts.from && row.laneId !== opts.from)
|
|
8022
|
+
return false;
|
|
8023
|
+
if (opts.project && row.projectId !== opts.project)
|
|
8024
|
+
return false;
|
|
8025
|
+
if (opts.story) {
|
|
8026
|
+
const lane = localLanes.get(row.laneId);
|
|
8027
|
+
if (!lane || lane.story_id !== opts.story)
|
|
8028
|
+
return false;
|
|
8029
|
+
}
|
|
8030
|
+
const audience = row.audience === "owner"
|
|
8031
|
+
? { kind: "user" }
|
|
8032
|
+
: row.audience === "operator"
|
|
8033
|
+
? { kind: "session", id: "mesh" }
|
|
8034
|
+
: { kind: "lane", id: row.laneId };
|
|
8035
|
+
if (forFilter && !audienceMatches(audience, forFilter))
|
|
8036
|
+
return false;
|
|
8037
|
+
return true;
|
|
8038
|
+
})
|
|
8039
|
+
.map((row) => ({
|
|
8040
|
+
question_id: row.questionId,
|
|
8041
|
+
mesh_question_id: row.questionId,
|
|
8042
|
+
lane_id: row.laneId,
|
|
8043
|
+
company,
|
|
8044
|
+
project_id: row.projectId,
|
|
8045
|
+
story_id: localLanes.get(row.laneId)?.story_id ?? "",
|
|
8046
|
+
...(row.threadId ? { thread_id: row.threadId } : {}),
|
|
8047
|
+
asked_at: row.askedAt,
|
|
8048
|
+
origin: { kind: "lane", id: row.laneId },
|
|
8049
|
+
hop: row.routingHistory.length || 1,
|
|
8050
|
+
status: row.status,
|
|
8051
|
+
audience: row.audience,
|
|
8052
|
+
host: `${row.host.kind}:${row.host.id}`,
|
|
8053
|
+
question: row.question,
|
|
8054
|
+
context: row.context ?? "",
|
|
8055
|
+
options: row.options,
|
|
8056
|
+
...(row.recommended ? { recommended: row.recommended } : {}),
|
|
8057
|
+
...(row.choice !== undefined ? { answer: { choice: row.choice, answered_by: row.answeredBy, answered_at: row.answeredAt, notes: row.notes } } : {}),
|
|
8058
|
+
})),
|
|
7838
8059
|
];
|
|
7839
8060
|
printJson({
|
|
7840
8061
|
ok: true,
|
|
@@ -7852,6 +8073,43 @@ async function runQuestionsShow(ctx, questionId) {
|
|
|
7852
8073
|
catch (err) {
|
|
7853
8074
|
if (!isLanesError(err) || err.error !== "question_not_found")
|
|
7854
8075
|
throw err;
|
|
8076
|
+
let meshCompany = null;
|
|
8077
|
+
try {
|
|
8078
|
+
meshCompany = await resolveActiveQuestionCompany(ctx, "Cannot look up Work Mesh question without an active company");
|
|
8079
|
+
}
|
|
8080
|
+
catch (companyError) {
|
|
8081
|
+
if (questionRecordRefusalCode(companyError) !== "question_company_unavailable")
|
|
8082
|
+
throw companyError;
|
|
8083
|
+
// The Outpost lookup below remains available to unscoped primary hosts.
|
|
8084
|
+
}
|
|
8085
|
+
if (meshCompany && await ctx.laneTrackerMeshEnabled(meshCompany)) {
|
|
8086
|
+
const rows = await createTrackerMeshQuestionClient({ company: meshCompany, request: ctx.trackerMeshRequest }).list();
|
|
8087
|
+
const matches = rows.filter((row) => row.questionId === questionId);
|
|
8088
|
+
if (matches.length > 1)
|
|
8089
|
+
throw new LanesError("tracker_mesh_question_ambiguous", `Work Mesh question ${questionId} is duplicated in the company inbox`, { question_id: questionId });
|
|
8090
|
+
const row = matches[0];
|
|
8091
|
+
if (row) {
|
|
8092
|
+
printJson({
|
|
8093
|
+
ok: true,
|
|
8094
|
+
action: "questions.show",
|
|
8095
|
+
question: {
|
|
8096
|
+
question_id: row.questionId,
|
|
8097
|
+
mesh_question_id: row.questionId,
|
|
8098
|
+
lane_id: row.laneId,
|
|
8099
|
+
company: meshCompany,
|
|
8100
|
+
project_id: row.projectId,
|
|
8101
|
+
thread_id: row.threadId,
|
|
8102
|
+
asked_at: row.askedAt,
|
|
8103
|
+
status: row.status,
|
|
8104
|
+
audience: row.audience,
|
|
8105
|
+
host: `${row.host.kind}:${row.host.id}`,
|
|
8106
|
+
},
|
|
8107
|
+
audience: row.audience,
|
|
8108
|
+
body: { question: row.question, context: row.context ?? "", options: row.options, ...(row.recommended ? { recommended: row.recommended } : {}), ...(row.notes ? { notes: row.notes } : {}) },
|
|
8109
|
+
});
|
|
8110
|
+
return;
|
|
8111
|
+
}
|
|
8112
|
+
}
|
|
7855
8113
|
const lookup = await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot);
|
|
7856
8114
|
if (lookup.errors.length > 0) {
|
|
7857
8115
|
throw new LanesError("outpost_question_lookup_unavailable", `Could not verify whether Outposts own question ${questionId}`, {
|
|
@@ -7907,6 +8165,42 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
|
|
|
7907
8165
|
if (!isLanesError(err) || err.error !== "question_not_found")
|
|
7908
8166
|
throw err;
|
|
7909
8167
|
const company = explicitCompany ?? await resolveActiveQuestionCompany(ctx, "Cannot look up this lane question because the active HQ company could not be resolved; re-run with --company <slug>");
|
|
8168
|
+
if (await ctx.laneTrackerMeshEnabled(company)) {
|
|
8169
|
+
const client = createTrackerMeshQuestionClient({ company, request: ctx.trackerMeshRequest });
|
|
8170
|
+
const matches = (await client.list(["pending", "answered"])).filter((row) => row.questionId === questionId);
|
|
8171
|
+
if (matches.length > 1)
|
|
8172
|
+
throw new LanesError("tracker_mesh_question_ambiguous", `Work Mesh question ${questionId} is duplicated in the company inbox`, { question_id: questionId });
|
|
8173
|
+
const row = matches[0];
|
|
8174
|
+
if (row) {
|
|
8175
|
+
if (row.status !== "pending")
|
|
8176
|
+
throw new LanesError("question_not_pending", `Question ${questionId} is ${row.status}`, { question_id: questionId, status: row.status });
|
|
8177
|
+
const freeform = opts.freeform?.trim() || undefined;
|
|
8178
|
+
if (freeform !== undefined && row.options.length === 0)
|
|
8179
|
+
throw new LanesError("freeform_not_allowed", "--freeform is only allowed for a question with options", { question_id: questionId });
|
|
8180
|
+
if (!freeform && !opts.choice)
|
|
8181
|
+
throw new LanesError("missing_choice", "--choice or --freeform is required", {});
|
|
8182
|
+
const choice = freeform ?? resolveAnswerChoice(opts.choice, row.options, false);
|
|
8183
|
+
const answer = await client.answer(questionId, choice, opts.notes);
|
|
8184
|
+
printJson({
|
|
8185
|
+
ok: true,
|
|
8186
|
+
action: "questions.answer",
|
|
8187
|
+
lane_id: answer.laneId,
|
|
8188
|
+
question: {
|
|
8189
|
+
question_id: answer.questionId,
|
|
8190
|
+
mesh_question_id: answer.questionId,
|
|
8191
|
+
lane_id: answer.laneId,
|
|
8192
|
+
project_id: answer.projectId,
|
|
8193
|
+
status: answer.status,
|
|
8194
|
+
choice: answer.choice,
|
|
8195
|
+
answered_by: answer.answeredBy,
|
|
8196
|
+
answered_at: answer.answeredAt,
|
|
8197
|
+
delivered_at: answer.deliveredAt ?? null,
|
|
8198
|
+
},
|
|
8199
|
+
delivery: "awaiting_host_poll",
|
|
8200
|
+
});
|
|
8201
|
+
return;
|
|
8202
|
+
}
|
|
8203
|
+
}
|
|
7910
8204
|
const lookup = await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot, company);
|
|
7911
8205
|
if (lookup.errors.length > 0) {
|
|
7912
8206
|
throw new LanesError("outpost_question_lookup_unavailable", `Could not verify whether Outposts own question ${questionId}`, {
|
|
@@ -7982,6 +8276,22 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
|
|
|
7982
8276
|
throw new LanesError("missing_choice", "--choice or --freeform is required", {});
|
|
7983
8277
|
}
|
|
7984
8278
|
const choice = freeform ?? resolveAnswerChoice(opts.choice, body.options, false);
|
|
8279
|
+
let remoteAnswer;
|
|
8280
|
+
let meshQuestionId = question.mesh_question_id;
|
|
8281
|
+
if (await ctx.laneTrackerMeshEnabled(questionCompany)) {
|
|
8282
|
+
const client = createTrackerMeshQuestionClient({ company: questionCompany, request: ctx.trackerMeshRequest });
|
|
8283
|
+
if (!meshQuestionId) {
|
|
8284
|
+
const indexed = await indexTrackerQuestion(ctx, hqRoot, askingLane, question, body, questionCompany, client);
|
|
8285
|
+
meshQuestionId = indexed.mesh_question_id;
|
|
8286
|
+
}
|
|
8287
|
+
if (!meshQuestionId)
|
|
8288
|
+
throw new LanesError("tracker_mesh_question_link_missing", "Work Mesh question could not be linked before answering", { question_id: question.question_id });
|
|
8289
|
+
remoteAnswer = await client.answer(meshQuestionId, choice, opts.notes);
|
|
8290
|
+
assertMeshQuestionMatchesLocal(remoteAnswer, question, questionCompany);
|
|
8291
|
+
if (remoteAnswer.status !== "answered" || remoteAnswer.choice !== choice) {
|
|
8292
|
+
throw new LanesError("tracker_mesh_response_invalid", "Work Mesh did not record the requested question answer", { mesh_question_id: meshQuestionId });
|
|
8293
|
+
}
|
|
8294
|
+
}
|
|
7985
8295
|
const at = ctx.now().toISOString();
|
|
7986
8296
|
const answeredBy = sessionIdFromEnv(ctx.env)
|
|
7987
8297
|
? `session:${sessionIdFromEnv(ctx.env)}`
|
|
@@ -8016,6 +8326,7 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
|
|
|
8016
8326
|
answered_at: at,
|
|
8017
8327
|
answered_level: current.hop,
|
|
8018
8328
|
},
|
|
8329
|
+
...(meshQuestionId ? { mesh_question_id: meshQuestionId } : {}),
|
|
8019
8330
|
};
|
|
8020
8331
|
});
|
|
8021
8332
|
const latestAskingLane = readLane(hqRoot, askingLane.lane_id);
|
|
@@ -8050,6 +8361,8 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
|
|
|
8050
8361
|
if (wake.status === "failed") {
|
|
8051
8362
|
throw new LanesError("question_wake_failed", `Question ${questionId} was recorded, but lane ${askingLane.lane_id} could not be woken: ${wake.error}`, { lane_id: askingLane.lane_id, question_id: questionId, wake_error: wake.error });
|
|
8052
8363
|
}
|
|
8364
|
+
if (remoteAnswer)
|
|
8365
|
+
await syncTrackerMeshQuestions(ctx, hqRoot);
|
|
8053
8366
|
printJson({
|
|
8054
8367
|
ok: true,
|
|
8055
8368
|
action: "questions.answer",
|
|
@@ -8709,6 +9022,7 @@ async function runReconcile(ctx, opts) {
|
|
|
8709
9022
|
livenessDeps: livenessDepsFromCtx(ctx),
|
|
8710
9023
|
presenceConnected: ctx.presenceConnected,
|
|
8711
9024
|
resolveQuestionActiveCompany: () => resolveActiveQuestionCompany(ctx),
|
|
9025
|
+
syncTrackerQuestions: (root) => syncTrackerMeshQuestions(ctx, root),
|
|
8712
9026
|
recoverQueuedMessages: async (lane) => {
|
|
8713
9027
|
await recoverQueuedMessages(ctx, lane.lane_id);
|
|
8714
9028
|
},
|
|
@@ -8789,6 +9103,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8789
9103
|
resolveCompany: ctx.resolveCompany,
|
|
8790
9104
|
trackerEnabled: ctx.laneTrackerEnabled,
|
|
8791
9105
|
trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
|
|
9106
|
+
meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
|
|
8792
9107
|
now: ctx.now,
|
|
8793
9108
|
actor: ctx.trackerActor,
|
|
8794
9109
|
}, wrap);
|
|
@@ -8797,6 +9112,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8797
9112
|
resolveCompany: ctx.resolveCompany,
|
|
8798
9113
|
trackerEnabled: ctx.laneTrackerEnabled,
|
|
8799
9114
|
trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
|
|
9115
|
+
meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
|
|
8800
9116
|
now: ctx.now,
|
|
8801
9117
|
}, wrap);
|
|
8802
9118
|
registerLaneChecksCommand(lanes, {
|
|
@@ -8804,6 +9120,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8804
9120
|
resolveCompany: ctx.resolveCompany,
|
|
8805
9121
|
trackerEnabled: ctx.laneTrackerEnabled,
|
|
8806
9122
|
trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
|
|
9123
|
+
meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
|
|
8807
9124
|
now: ctx.now,
|
|
8808
9125
|
}, wrap);
|
|
8809
9126
|
registerLaneReportCommands(lanes, {
|
|
@@ -8811,6 +9128,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
8811
9128
|
resolveCompany: ctx.resolveCompany,
|
|
8812
9129
|
trackerEnabled: ctx.laneTrackerEnabled,
|
|
8813
9130
|
trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
|
|
9131
|
+
meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
|
|
8814
9132
|
now: ctx.now,
|
|
8815
9133
|
readQuestionBody: (root, question) => {
|
|
8816
9134
|
const body = requireQuestionBody(root, question);
|
|
@@ -11,7 +11,6 @@ import { type CompanyBriefResult } from "./company-brief.js";
|
|
|
11
11
|
import { type OntologyCompany } from "./company.js";
|
|
12
12
|
import { type MultiWriterStart } from "./multi-writer.js";
|
|
13
13
|
export declare const ONTOLOGY_FLAG = "checkpoint.ontology";
|
|
14
|
-
export declare const SHARED_INPUTS_FLAG = "checkpoint.ontology.shared-inputs";
|
|
15
14
|
export declare const COMPANY_BRIEF_FLAG = "checkpoint.ontology.company-brief";
|
|
16
15
|
/**
|
|
17
16
|
* Applies only when the payload's sibling backend is grok. On gold-v2, Claude
|
|
@@ -43,7 +42,7 @@ export declare const BUDGET: {
|
|
|
43
42
|
readonly maxTokens: 60000;
|
|
44
43
|
readonly chunkTokens: 12000;
|
|
45
44
|
};
|
|
46
|
-
export type PendingReason = "session-touched"
|
|
45
|
+
export type PendingReason = "session-touched";
|
|
47
46
|
export interface LedgerEntry {
|
|
48
47
|
sha256: string;
|
|
49
48
|
size: number;
|
|
@@ -89,7 +88,6 @@ export interface OntologyPlan {
|
|
|
89
88
|
created_at: string;
|
|
90
89
|
first_run: boolean;
|
|
91
90
|
boundary: string | null;
|
|
92
|
-
shared_inputs: boolean;
|
|
93
91
|
/** Present only when `checkpoint.ontology.company-brief` was on for this plan. */
|
|
94
92
|
company_brief?: {
|
|
95
93
|
status: CompanyBriefResult["status"];
|
|
@@ -152,10 +150,10 @@ export declare function sessionTouched(hqRoot: string, company: OntologyCompany,
|
|
|
152
150
|
export declare function chunkLines(content: string, chunkTokens: number): Array<[number, number]>;
|
|
153
151
|
/**
|
|
154
152
|
* Update the ledger with the current tree and return it. New or changed files
|
|
155
|
-
*
|
|
156
|
-
*
|
|
153
|
+
* the session touched become pending; others are recorded as seen. The first
|
|
154
|
+
* run records the entity boundary (design 14.3).
|
|
157
155
|
*/
|
|
158
|
-
export declare function refreshLedger(company: OntologyCompany, existing: Ledger | null, touched: Set<string
|
|
156
|
+
export declare function refreshLedger(company: OntologyCompany, existing: Ledger | null, touched: Set<string>): {
|
|
159
157
|
ledger: Ledger;
|
|
160
158
|
firstRun: boolean;
|
|
161
159
|
};
|
|
@@ -15,7 +15,6 @@ import { resolveOntologyCompany, safeWriteFile, stateRoot } from "./company.js";
|
|
|
15
15
|
import { startMultiWriterRun } from "./multi-writer.js";
|
|
16
16
|
import { buildEntityIndex, listKnowledgeFiles, listSignalFiles, readSignal } from "./tree.js";
|
|
17
17
|
export const ONTOLOGY_FLAG = "checkpoint.ontology";
|
|
18
|
-
export const SHARED_INPUTS_FLAG = "checkpoint.ontology.shared-inputs";
|
|
19
18
|
export const COMPANY_BRIEF_FLAG = "checkpoint.ontology.company-brief";
|
|
20
19
|
/**
|
|
21
20
|
* Applies only when the payload's sibling backend is grok. On gold-v2, Claude
|
|
@@ -163,14 +162,13 @@ function missingRefs(companyDir, rel) {
|
|
|
163
162
|
}
|
|
164
163
|
/**
|
|
165
164
|
* Update the ledger with the current tree and return it. New or changed files
|
|
166
|
-
*
|
|
167
|
-
*
|
|
165
|
+
* the session touched become pending; others are recorded as seen. The first
|
|
166
|
+
* run records the entity boundary (design 14.3).
|
|
168
167
|
*/
|
|
169
|
-
export function refreshLedger(company, existing, touched
|
|
168
|
+
export function refreshLedger(company, existing, touched) {
|
|
170
169
|
const firstRun = existing === null;
|
|
171
170
|
const index = firstRun ? buildEntityIndex(company.dir) : null;
|
|
172
171
|
const boundary = existing ? existing.boundary : entityBoundary(index);
|
|
173
|
-
const boundaryMs = boundary ? Date.parse(boundary) : Number.NaN;
|
|
174
172
|
const entries = {};
|
|
175
173
|
const files = [...listKnowledgeFiles(company.dir), ...listSignalFiles(company.dir)];
|
|
176
174
|
for (const rel of files) {
|
|
@@ -188,24 +186,10 @@ export function refreshLedger(company, existing, touched, sharedInputs) {
|
|
|
188
186
|
: { ...previous, ...base };
|
|
189
187
|
continue;
|
|
190
188
|
}
|
|
191
|
-
|
|
192
|
-
if (touched.has(rel))
|
|
193
|
-
reason = "session-touched";
|
|
194
|
-
else if (sharedInputs && firstRun) {
|
|
195
|
-
if (Number.isFinite(boundaryMs) && stat.mtimeMs > boundaryMs)
|
|
196
|
-
reason = "first-run-gap";
|
|
197
|
-
else if (rel.startsWith("signals/") && missingRefs(company.dir, rel).length > 0)
|
|
198
|
-
reason = "missing-refs";
|
|
199
|
-
}
|
|
200
|
-
else if (sharedInputs)
|
|
201
|
-
reason = "shared";
|
|
202
|
-
entries[rel] = reason ? { ...base, state: "pending", reason } : { ...base, state: "seen" };
|
|
189
|
+
entries[rel] = touched.has(rel) ? { ...base, state: "pending", reason: "session-touched" } : { ...base, state: "seen" };
|
|
203
190
|
}
|
|
204
191
|
return { ledger: { schema: LEDGER_SCHEMA, company: company.slug, boundary, entries }, firstRun };
|
|
205
192
|
}
|
|
206
|
-
function reasonRank(reason) {
|
|
207
|
-
return reason === "session-touched" ? 0 : 1;
|
|
208
|
-
}
|
|
209
193
|
/** Plan this run. Never throws for a skip; returns the reason instead. */
|
|
210
194
|
export async function planOntologyRun(options) {
|
|
211
195
|
const { hqRoot, runDir, payload } = options;
|
|
@@ -218,7 +202,6 @@ export async function planOntologyRun(options) {
|
|
|
218
202
|
fs.rmSync(backendPreferencePath(hqRoot, company), { force: true });
|
|
219
203
|
return { status: "skipped", reason: "flag off" };
|
|
220
204
|
}
|
|
221
|
-
const sharedInputs = await options.readFlag(SHARED_INPUTS_FLAG, company);
|
|
222
205
|
const briefOn = await options.readFlag(COMPANY_BRIEF_FLAG, company);
|
|
223
206
|
const ignoreAnnotations = (await options.readFlag(IGNORE_ANNOTATIONS_FLAG, company)) && payload.backend === "grok";
|
|
224
207
|
recordBackendPreference(hqRoot, company, await options.readFlag(GROK_BACKEND_FLAG, company));
|
|
@@ -226,15 +209,15 @@ export async function planOntologyRun(options) {
|
|
|
226
209
|
const touched = sessionTouched(hqRoot, company, payload.files);
|
|
227
210
|
// A first run takes its boundary from the entity tree, so the ledger is
|
|
228
211
|
// refreshed before merge-back moves any `last_updated` forward.
|
|
229
|
-
const { ledger, firstRun } = refreshLedger(company, readLedger(hqRoot, company), touched
|
|
212
|
+
const { ledger, firstRun } = refreshLedger(company, readLedger(hqRoot, company), touched);
|
|
230
213
|
// Merge-back and the brief come before the nothing-changed return, so such
|
|
231
214
|
// a plan still merges set-aside versions and records the brief; the brief
|
|
232
215
|
// follows merge-back so its counts include merged entities.
|
|
233
216
|
const multiWriter = multiWriterOn ? startMultiWriterRun(hqRoot, company, options.now ?? new Date()) : undefined;
|
|
234
217
|
const companyBrief = briefOn ? writeCompanyBrief(hqRoot, company, options.now ?? new Date()) : undefined;
|
|
235
218
|
const pending = Object.entries(ledger.entries)
|
|
236
|
-
.filter(([, entry]) => entry.state === "pending" &&
|
|
237
|
-
.sort(([aRel, a], [bRel, b]) =>
|
|
219
|
+
.filter(([, entry]) => entry.state === "pending" && entry.reason === "session-touched")
|
|
220
|
+
.sort(([aRel, a], [bRel, b]) => a.mtime_ms - b.mtime_ms || aRel.localeCompare(bRel));
|
|
238
221
|
const inputs = [];
|
|
239
222
|
let tokens = 0;
|
|
240
223
|
let remaining = 0;
|
|
@@ -264,7 +247,7 @@ export async function planOntologyRun(options) {
|
|
|
264
247
|
kind,
|
|
265
248
|
path: rel,
|
|
266
249
|
audience: "company",
|
|
267
|
-
reason:
|
|
250
|
+
reason: "session-touched",
|
|
268
251
|
sha256: entry.sha256,
|
|
269
252
|
est_tokens: cost,
|
|
270
253
|
lines: chunks.length > 1 ? [start, end] : null,
|
|
@@ -313,7 +296,6 @@ export async function planOntologyRun(options) {
|
|
|
313
296
|
created_at: (options.now ?? new Date()).toISOString(),
|
|
314
297
|
first_run: firstRun,
|
|
315
298
|
boundary: ledger.boundary,
|
|
316
|
-
shared_inputs: sharedInputs,
|
|
317
299
|
...(companyBrief
|
|
318
300
|
? {
|
|
319
301
|
company_brief: companyBrief.reason
|
|
@@ -81,6 +81,8 @@ export interface ReconcileDeps {
|
|
|
81
81
|
readRegistryFlag?: typeof readLanesRegistryFlag;
|
|
82
82
|
/** Active-company fallback for questions raised from an unscoped lane. */
|
|
83
83
|
resolveQuestionActiveCompany?: () => Promise<LaneCompanyRef>;
|
|
84
|
+
/** Index local questions and poll remote answers before the normal answer-wake pass. */
|
|
85
|
+
syncTrackerQuestions?: (hqRoot: string) => Promise<void>;
|
|
84
86
|
/** Resume queued messages after this pass wins a local dead-worker CAS. */
|
|
85
87
|
recoverQueuedMessages?: (lane: LaneRecord) => Promise<void>;
|
|
86
88
|
/** Detached mesh seam for dead-watcher recovery. */
|
|
@@ -632,6 +632,14 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
632
632
|
const livenessDeps = deps.livenessDeps ?? defaultLivenessDeps();
|
|
633
633
|
let lanes;
|
|
634
634
|
let questions;
|
|
635
|
+
if (!deps.dryRun) {
|
|
636
|
+
try {
|
|
637
|
+
await deps.syncTrackerQuestions?.(hqRoot);
|
|
638
|
+
}
|
|
639
|
+
catch (err) {
|
|
640
|
+
markError(summary, err);
|
|
641
|
+
}
|
|
642
|
+
}
|
|
635
643
|
try {
|
|
636
644
|
lanes = loadLaneRecords(hqRoot, summary);
|
|
637
645
|
questions = loadQuestionRecords(hqRoot, summary);
|
|
@@ -845,6 +853,14 @@ export async function runPassBody(hqRoot, deps) {
|
|
|
845
853
|
await admitQueued(hqRoot, drainLanes, summary, deps, now);
|
|
846
854
|
await resumeAwaiting(hqRoot, drainLanes, freshQuestions, summary, deps);
|
|
847
855
|
});
|
|
856
|
+
if (!deps.dryRun && deps.syncTrackerQuestions) {
|
|
857
|
+
try {
|
|
858
|
+
await deps.syncTrackerQuestions(hqRoot);
|
|
859
|
+
}
|
|
860
|
+
catch (err) {
|
|
861
|
+
markError(summary, err);
|
|
862
|
+
}
|
|
863
|
+
}
|
|
848
864
|
if (!deps.dryRun && summary.errors.length === 0) {
|
|
849
865
|
try {
|
|
850
866
|
writeHeartbeatFile(hqRoot, summary, nowIso);
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -708,7 +708,15 @@ export function parseQuestionRecord(raw, filePath) {
|
|
|
708
708
|
value: delivered_at,
|
|
709
709
|
});
|
|
710
710
|
}
|
|
711
|
-
|
|
711
|
+
const mesh_question_id = raw.mesh_question_id;
|
|
712
|
+
if (mesh_question_id !== undefined && (typeof mesh_question_id !== "string" || !mesh_question_id.trim())) {
|
|
713
|
+
throw new LaneSchemaError("mesh_question_id must be a non-empty string when present", { path: filePath, value: mesh_question_id });
|
|
714
|
+
}
|
|
715
|
+
const mesh_delivered_at = raw.mesh_delivered_at === undefined ? null : raw.mesh_delivered_at;
|
|
716
|
+
if (mesh_delivered_at !== null && (typeof mesh_delivered_at !== "string" || !Number.isFinite(Date.parse(mesh_delivered_at)))) {
|
|
717
|
+
throw new LaneSchemaError("mesh_delivered_at must be a valid timestamp or null", { path: filePath, value: mesh_delivered_at });
|
|
718
|
+
}
|
|
719
|
+
return { ...raw, delivered_at, mesh_delivered_at };
|
|
712
720
|
}
|
|
713
721
|
/**
|
|
714
722
|
* HQ Sync sibling-twin grammar from `@indigoai-us/hq-cloud` `buildConflictPath`:
|