@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.
@@ -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
- const withBody = raiseLaneQuestion({
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" | "shared" | "first-run-gap" | "missing-refs";
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
- * become pending when this machine should process them (design 14.1) and
156
- * seen otherwise. The first run seeds from the entity boundary (design 14.3).
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>, sharedInputs: boolean): {
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
- * become pending when this machine should process them (design 14.1) and
167
- * seen otherwise. The first run seeds from the entity boundary (design 14.3).
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, sharedInputs) {
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
- let reason = null;
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, sharedInputs);
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" && (sharedInputs || entry.reason === "session-touched"))
237
- .sort(([aRel, a], [bRel, b]) => reasonRank(a.reason) - reasonRank(b.reason) || a.mtime_ms - b.mtime_ms || aRel.localeCompare(bRel));
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: entry.reason ?? "shared",
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);
@@ -708,7 +708,15 @@ export function parseQuestionRecord(raw, filePath) {
708
708
  value: delivered_at,
709
709
  });
710
710
  }
711
- return { ...raw, delivered_at };
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`: