@indigoai-us/hq-cli 5.225.0 → 5.227.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.
Files changed (50) hide show
  1. package/CHANGELOG.md +47 -1
  2. package/dist/command-catalog.generated.d.ts +100 -0
  3. package/dist/command-catalog.generated.js +128 -0
  4. package/dist/command-registration-plan.d.ts +18 -0
  5. package/dist/command-registration-plan.js +6 -0
  6. package/dist/commands/agent-enroll.d.ts +2 -0
  7. package/dist/commands/agent-enroll.js +17 -2
  8. package/dist/commands/cloud.d.ts +10 -0
  9. package/dist/commands/cloud.js +52 -47
  10. package/dist/commands/lanes-checks.d.ts +2 -0
  11. package/dist/commands/lanes-checks.js +13 -11
  12. package/dist/commands/lanes-import.d.ts +1 -0
  13. package/dist/commands/lanes-import.js +12 -11
  14. package/dist/commands/lanes-report.js +21 -14
  15. package/dist/commands/lanes-tracker.d.ts +1 -0
  16. package/dist/commands/lanes-tracker.js +39 -31
  17. package/dist/commands/lanes.d.ts +8 -0
  18. package/dist/commands/lanes.js +818 -314
  19. package/dist/commands/staff/reset-test-account.d.ts +110 -0
  20. package/dist/commands/staff/reset-test-account.js +268 -0
  21. package/dist/commands/staff/test-account.d.ts +51 -0
  22. package/dist/commands/staff/test-account.js +196 -0
  23. package/dist/lib/checkpoint-ontology/plan.d.ts +4 -6
  24. package/dist/lib/checkpoint-ontology/plan.js +8 -26
  25. package/dist/lib/doctor/client-health-flag-token.d.ts +15 -0
  26. package/dist/lib/doctor/client-health-flag-token.js +66 -0
  27. package/dist/lib/doctor/client-health-reason-codes-flag.d.ts +1 -0
  28. package/dist/lib/doctor/client-health-reason-codes-flag.js +16 -5
  29. package/dist/lib/doctor/client-health-sentry-flag.d.ts +1 -0
  30. package/dist/lib/doctor/client-health-sentry-flag.js +17 -6
  31. package/dist/lib/lanes/dropbox.d.ts +9 -0
  32. package/dist/lib/lanes/dropbox.js +70 -1
  33. package/dist/lib/lanes/reconcile.d.ts +4 -0
  34. package/dist/lib/lanes/reconcile.js +28 -0
  35. package/dist/lib/lanes/store.js +9 -1
  36. package/dist/lib/lanes/tracker-mesh-questions.d.ts +54 -0
  37. package/dist/lib/lanes/tracker-mesh-questions.js +138 -0
  38. package/dist/lib/lanes/tracker-mesh.d.ts +19 -0
  39. package/dist/lib/lanes/tracker-mesh.js +228 -0
  40. package/dist/lib/lanes/tracker-store.d.ts +13 -2
  41. package/dist/lib/lanes/tracker-store.js +1 -1
  42. package/dist/lib/lanes/types.d.ts +4 -0
  43. package/dist/lib/lanes/watcher-main.js +16 -0
  44. package/dist/lib/lanes/watcher.d.ts +4 -1
  45. package/dist/lib/lanes/watcher.js +77 -3
  46. package/dist/utils/cognito-session.d.ts +8 -0
  47. package/dist/utils/cognito-session.js +198 -3
  48. package/dist/utils/upload-author.d.ts +15 -0
  49. package/dist/utils/upload-author.js +39 -0
  50. package/package.json +1 -1
@@ -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,19 +46,20 @@ 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";
51
53
  import { parseCliEffort, resolveLaneEffort, } from "../lib/lanes/effort.js";
52
54
  import { LanesError } from "../lib/lanes/errors.js";
53
55
  import { dispatchLaneDelivery } from "../lib/lanes/delivery-core.js";
54
- import { formatMonitorEvent } from "../lib/lanes/dropbox.js";
56
+ import { dropboxInboxHasMessages, drainDropbox, formatDropboxMessages, formatMonitorEvent, requeueDropboxMessages, } from "../lib/lanes/dropbox.js";
57
+ import { isPidAlive, runWatcherLoop, WATCHER_OUT_MAX_BYTES, watcherOutLogPath, writeWatcherDecision, } from "../lib/lanes/watcher.js";
55
58
  import { accountQuotaView, loadQuotaState, quotaBlockFor } from "../lib/lanes/quota.js";
56
59
  import { SESSION_MOVE_REASON } from "../lib/lanes/move-account.js";
57
60
  import { ensureSessionHome } from "../lib/lanes/session-relink.js";
58
61
  import { defaultProbe, validateProfileLaneModel, validateLaneModel, } from "../lib/lanes/models.js";
59
- import { parseCliModel, refuseOutpostSelfTarget, resolveLaneModel, FOREGROUND_ONLY_RULE, STANDING_AUTHORIZATION, spawnLaneWorker, laneRunDir, readSpawnManifest, stopLaneProcesses, } from "../lib/lanes/spawn.js";
62
+ import { parseCliModel, refuseOutpostSelfTarget, resolveLaneModel, FOREGROUND_ONLY_RULE, STANDING_AUTHORIZATION, spawnLaneWorker, laneRunDir, readSpawnManifest, readPidRecord, probePidRecord, readProcStarttime, stopLaneProcesses, } from "../lib/lanes/spawn.js";
60
63
  import { handOffInstallHere } from "../lib/daemon/handoff.js";
61
64
  export { spawnLaneWorker };
62
65
  export { questionBodyAbsPath };
@@ -283,6 +286,8 @@ function resolveDeps(deps = {}) {
283
286
  proveDetached: deps.proveDetached,
284
287
  killProcess: deps.killProcess,
285
288
  isAlive: deps.isAlive,
289
+ readStarttime: deps.readStarttime,
290
+ watcherLoopDeps: deps.watcherLoopDeps,
286
291
  stopSleepMs: deps.stopSleepMs,
287
292
  livenessDeps: deps.livenessDeps,
288
293
  presenceConnected: deps.presenceConnected,
@@ -324,6 +329,7 @@ function resolveDeps(deps = {}) {
324
329
  ((company) => defaultLaneOutpostQuestionVisibilityEnabled(company, env)),
325
330
  laneTrackerEnabled: deps.laneTrackerEnabled ?? defaultLaneTrackerEnabled,
326
331
  laneTrackerMeshEnabled: deps.laneTrackerMeshEnabled ?? defaultLaneTrackerMeshEnabled,
332
+ trackerMeshRequest: deps.trackerMeshRequest,
327
333
  laneHostScopedAccountHomesEnabled: deps.laneHostScopedAccountHomesEnabled ??
328
334
  ((company) => defaultLaneHostScopedAccountHomesEnabled(company, env)),
329
335
  trackerActor: deps.trackerActor ?? defaultTrackerActor,
@@ -513,6 +519,167 @@ function requireQuestionBody(hqRoot, question) {
513
519
  }
514
520
  return body;
515
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
+ }
516
683
  function loadAllLanes(hqRoot) {
517
684
  return listLaneIds(hqRoot).map((id) => readLane(hqRoot, id));
518
685
  }
@@ -5552,7 +5719,13 @@ function assertCurrentResumeAccountAvailable(ctx, lane) {
5552
5719
  }
5553
5720
  async function runMessage(ctx, laneId, opts) {
5554
5721
  const hqRoot = ctx.hqRoot();
5555
- const text = opts.text;
5722
+ let accepted = false;
5723
+ let claimedMessages = [];
5724
+ const markAccepted = () => {
5725
+ accepted = true;
5726
+ opts.onAccepted?.();
5727
+ };
5728
+ let text = opts.text;
5556
5729
  if (!text) {
5557
5730
  throw new LanesError("missing_text", "--text is required", {});
5558
5731
  }
@@ -5562,330 +5735,498 @@ async function runMessage(ctx, laneId, opts) {
5562
5735
  return;
5563
5736
  }
5564
5737
  assertLaneRunsHereForOperatorMutation(ctx, lane, "message");
5565
- const cliEffort = parseCliEffort(lane.provider, opts.effort);
5566
- if (cliEffort !== undefined && !providerSupportsSessionResume(lane.provider)) {
5567
- throw new LanesError("effort_resume_unsupported", `Provider ${lane.provider} cannot change effort on a resumed session`, { lane_id: laneId, provider: lane.provider });
5568
- }
5569
- // Check before transcript recovery, account probing, or message writes.
5570
- // The final state transition repeats this check under the senior lock.
5571
- await withResumeHireAdmission(ctx, lane, () => undefined);
5572
- lane = laneWithRecoveredProviderSession(hqRoot, lane);
5573
- const keepCurrentAccount = opts.account !== undefined && accountRequestKeepsCurrentLane(lane, opts.account);
5574
- if (keepCurrentAccount)
5575
- assertCurrentResumeAccountAvailable(ctx, lane);
5576
- // A real account move is refused on its own terms first: a running lane and
5577
- // a provider without session resume get the move's named errors, not the
5578
- // generic delivery refusal below. Naming the current account is a keep.
5579
- if (opts.account !== undefined && !keepCurrentAccount && lane.state === "running") {
5580
- throw new LanesError("lane_running", `Lane ${laneId} is running; stop it or wait for it to exit before moving its session`, {
5581
- lane_id: laneId,
5582
- state: lane.state,
5583
- });
5584
- }
5585
- if (opts.account !== undefined && !keepCurrentAccount && !providerSessionPortable(lane.provider)) {
5586
- throw new LanesError("provider_session_not_portable", sessionNotPortableMessage(lane.provider), { lane_id: laneId, provider: lane.provider });
5587
- }
5588
- // One decision point for how a follow-up reaches this lane (delivery.ts).
5589
- // `none` refuses before anything is written: no inbox append, no spawn.
5590
- const mechanism = laneDeliveryMechanism(lane);
5591
- if (mechanism === "none") {
5592
- const message = deliveryRefusalReason(lane);
5593
- throw new LanesError("lane_not_accepting_messages", message, {
5594
- lane_id: laneId,
5595
- state: lane.state,
5596
- provider: lane.provider,
5597
- message,
5598
- });
5599
- }
5600
- if (keepCurrentAccount && opts.model !== undefined && mechanism !== "resume") {
5601
- throw new LanesError("model_requires_resume", "--model with --account requires a message that resumes the lane", { lane_id: laneId, state: lane.state });
5602
- }
5603
- if (opts.worker !== undefined && mechanism === "dropbox") {
5604
- throw new LanesError("worker_profile_requires_resume", `Lane ${laneId} is still running; --worker can only be applied when message starts a resumed provider turn`, { lane_id: laneId, state: lane.state });
5605
- }
5606
- if (cliEffort !== undefined && mechanism === "dropbox") {
5607
- throw new LanesError("lane_effort_requires_resume", `Lane ${laneId} is still running; --effort can only be applied when message starts a resumed provider turn`, { lane_id: laneId, state: lane.state });
5608
- }
5609
- const resumeProfile = opts.worker !== undefined
5610
- ? resolveResumeProfile(ctx, lane, opts.worker)
5611
- : undefined;
5612
- if (opts.model !== undefined && opts.account === undefined) {
5613
- throw new LanesError("model_requires_account", "--model on hq lanes message is only accepted together with --account", {});
5614
- }
5615
- const automaticAccountSelection = opts.account === undefined && mechanism === "resume"
5616
- ? await selectAutomaticClaudeResumeAccount(ctx, lane, resumeProfile)
5617
- : undefined;
5618
- const resumeEffort = resolveResumeEffort(ctx, lane, {
5619
- cliEffort,
5620
- profile: resumeProfile,
5621
- account: keepCurrentAccount ? lane.account : opts.account ?? automaticAccountSelection?.selection.account,
5622
- model: opts.model,
5623
- });
5624
- const messageId = mintMessageId();
5625
- const at = ctx.now().toISOString();
5626
- const projectId = opts.project?.trim() || lane.project_id;
5627
- const storyId = opts.story?.trim() || lane.story_id;
5628
- if (opts.story !== undefined && !opts.story.trim()) {
5629
- throw new LanesError("missing_story", "--story must be non-empty", {});
5630
- }
5631
- if ((opts.project !== undefined || opts.story !== undefined) && ctx.storyLedgerEnabled()) {
5632
- validateProjectStories(hqRoot, projectId, [storyId], lane.company?.slug);
5738
+ if (lane.state === "running") {
5739
+ const workerPidPath = path.join(laneRunDir(hqRoot, laneId), "worker.pid");
5740
+ const workerRecordExists = fs.existsSync(workerPidPath);
5741
+ const worker = readPidRecord(workerPidPath);
5742
+ if (workerRecordExists && worker === null) {
5743
+ throw new LanesError("worker_pid_invalid", `Lane ${laneId} has a malformed worker PID record; message delivery cannot be classified safely`, { lane_id: laneId, worker_pid_path: workerPidPath });
5744
+ }
5745
+ // A fresh spawn may have committed running before publishing worker.pid;
5746
+ // absence is not proof of exit. Reclassify only a recorded process that
5747
+ // fails the PID plus start-time identity probe.
5748
+ const processAlive = worker === null
5749
+ ? true
5750
+ : probePidRecord(worker, ctx.isAlive ?? isPidAlive, ctx.readStarttime ?? readProcStarttime).alive;
5751
+ if (worker !== null && !processAlive) {
5752
+ // The watcher may not have reached its next poll yet. Run the same
5753
+ // classifier synchronously, then make the queue/resume choice again.
5754
+ await runWatcherLoop(hqRoot, laneId, {
5755
+ ...ctx.watcherLoopDeps,
5756
+ now: () => ctx.now().getTime(),
5757
+ isAlive: ctx.isAlive ?? isPidAlive,
5758
+ readStarttime: ctx.readStarttime ?? readProcStarttime,
5759
+ mesh: null,
5760
+ resolveQuestionActiveCompany: () => resolveActiveQuestionCompany(ctx),
5761
+ });
5762
+ lane = readLane(hqRoot, laneId);
5763
+ // This invocation won the race to classify the dead worker, so the
5764
+ // watcher callback did not own recovery. Fold any earlier queued
5765
+ // messages into this resume to keep one provider turn and one delivery.
5766
+ if (laneDeliveryMechanism(lane) === "resume" && dropboxInboxHasMessages(hqRoot, laneId)) {
5767
+ const queuedMessages = drainDropbox(hqRoot, laneId);
5768
+ if (queuedMessages.length > 0) {
5769
+ claimedMessages = queuedMessages;
5770
+ text = `${formatDropboxMessages(queuedMessages)}\n\n${text}`;
5771
+ }
5772
+ }
5773
+ }
5633
5774
  }
5634
- const framedMonitorEvent = opts.monitorEvent === undefined
5635
- ? undefined
5636
- : formatMonitorEvent({
5637
- monitorId: opts.monitorEvent.monitor_id,
5638
- description: opts.monitorEvent.description,
5639
- sequence: opts.monitorEvent.sequence,
5640
- text,
5641
- });
5642
- const deliveryText = framedMonitorEvent ?? text;
5643
- const dropboxMessage = {
5644
- at,
5645
- message_id: messageId,
5646
- text,
5647
- project_id: projectId,
5648
- story_id: storyId,
5649
- ...(opts.monitorEvent === undefined ? {} : {
5650
- kind: "monitor_event",
5651
- monitor_id: opts.monitorEvent.monitor_id,
5652
- description: opts.monitorEvent.description,
5653
- sequence: opts.monitorEvent.sequence,
5654
- }),
5655
- };
5656
- let resumed = null;
5657
- let move = null;
5658
- let queuedOnAdmission = false;
5659
- if (opts.account !== undefined && !keepCurrentAccount) {
5660
- // A moved message is always a resume on the target account: a running
5661
- // lane (the dropbox mechanism) is refused inside prepareAccountMove.
5662
- move = await prepareAccountMove(ctx, lane, {
5663
- account: opts.account,
5664
- cliModel: opts.model?.trim() || undefined,
5665
- ...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
5666
- ...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
5667
- pending: {
5668
- trigger: "message",
5669
- text: deliveryText,
5670
- ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
5671
- message_id: messageId,
5672
- compact: opts.compact !== false,
5673
- },
5674
- });
5675
- lane = move.lane;
5676
- if (move.queued) {
5677
- archiveResumeMessage(hqRoot, laneId, dropboxMessage);
5678
- }
5679
- }
5680
- else if (mechanism === "resume") {
5681
- const automatic = automaticAccountSelection;
5682
- if (automatic !== undefined) {
5683
- const selection = automatic.selection;
5684
- if (selection.account !== lane.account) {
5685
- move = await prepareAccountMove(ctx, lane, {
5686
- account: selection.account,
5687
- cliModel: undefined,
5688
- ...(automatic.historyReason !== undefined ? { accountHistoryReason: automatic.historyReason } : {}),
5689
- ...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
5690
- ...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
5691
- pending: {
5692
- trigger: "message",
5693
- text: deliveryText,
5694
- ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
5695
- message_id: messageId,
5696
- compact: opts.compact !== false,
5697
- },
5698
- accountSelection: selection,
5699
- });
5700
- lane = move.lane;
5701
- if (move.queued)
5702
- archiveResumeMessage(hqRoot, laneId, dropboxMessage);
5775
+ try {
5776
+ const exclusion = messageLaneExclusion(ctx, lane);
5777
+ if (exclusion !== undefined) {
5778
+ let messageCount;
5779
+ if (dropboxInboxHasMessages(hqRoot, laneId)) {
5780
+ messageCount = drainDropbox(hqRoot, laneId, { archiveDirectory: "undeliverable" }).length;
5703
5781
  }
5704
- else {
5705
- lane = persistClaudeAccountSelection(hqRoot, lane, selection);
5782
+ recordMessageUndeliverable(ctx, lane, exclusion, messageCount);
5783
+ const refusalMessage = deliveryRefusalReason(lane);
5784
+ throw new LanesError("lane_not_accepting_messages", refusalMessage || `Lane ${laneId} cannot receive messages: ${exclusion}`, { lane_id: laneId, state: lane.state, reason: exclusion });
5785
+ }
5786
+ if (lane.state === "done" && dropboxInboxHasMessages(hqRoot, laneId)) {
5787
+ const staleMessages = drainDropbox(hqRoot, laneId, { archiveDirectory: "undeliverable" });
5788
+ if (staleMessages.length > 0) {
5789
+ recordMessageUndeliverable(ctx, lane, "done_before_queued_message", staleMessages.length);
5706
5790
  }
5707
5791
  }
5708
- }
5709
- if (resumeProfile !== undefined && (opts.account === undefined || keepCurrentAccount)) {
5710
- validateResumeProfileModel(ctx, lane, resumeProfile.profile);
5711
- }
5712
- if (move === null && (resumeProfile !== undefined || resumeEffort !== undefined || (keepCurrentAccount && opts.model !== undefined))) {
5713
- lane = recordResumeSetup(ctx, lane, {
5714
- ...(resumeProfile !== undefined ? { profile: resumeProfile } : {}),
5715
- ...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
5716
- ...(keepCurrentAccount && opts.model !== undefined ? { cliModel: opts.model } : {}),
5792
+ const cliEffort = parseCliEffort(lane.provider, opts.effort);
5793
+ if (cliEffort !== undefined && !providerSupportsSessionResume(lane.provider)) {
5794
+ throw new LanesError("effort_resume_unsupported", `Provider ${lane.provider} cannot change effort on a resumed session`, { lane_id: laneId, provider: lane.provider });
5795
+ }
5796
+ // Check before transcript recovery, account probing, or message writes.
5797
+ // The final state transition repeats this check under the senior lock.
5798
+ await withResumeHireAdmission(ctx, lane, () => undefined);
5799
+ lane = laneWithRecoveredProviderSession(hqRoot, lane);
5800
+ const keepCurrentAccount = opts.account !== undefined && accountRequestKeepsCurrentLane(lane, opts.account);
5801
+ if (keepCurrentAccount)
5802
+ assertCurrentResumeAccountAvailable(ctx, lane);
5803
+ // A real account move is refused on its own terms first: a running lane and
5804
+ // a provider without session resume get the move's named errors, not the
5805
+ // generic delivery refusal below. Naming the current account is a keep.
5806
+ if (opts.account !== undefined && !keepCurrentAccount && lane.state === "running") {
5807
+ throw new LanesError("lane_running", `Lane ${laneId} is running; stop it or wait for it to exit before moving its session`, {
5808
+ lane_id: laneId,
5809
+ state: lane.state,
5810
+ });
5811
+ }
5812
+ if (opts.account !== undefined && !keepCurrentAccount && !providerSessionPortable(lane.provider)) {
5813
+ throw new LanesError("provider_session_not_portable", sessionNotPortableMessage(lane.provider), { lane_id: laneId, provider: lane.provider });
5814
+ }
5815
+ // One decision point for how a follow-up reaches this lane (delivery.ts).
5816
+ // `none` refuses before anything is written: no inbox append, no spawn.
5817
+ const mechanism = laneDeliveryMechanism(lane);
5818
+ if (mechanism === "none") {
5819
+ const message = deliveryRefusalReason(lane);
5820
+ throw new LanesError("lane_not_accepting_messages", message, {
5821
+ lane_id: laneId,
5822
+ state: lane.state,
5823
+ provider: lane.provider,
5824
+ message,
5825
+ });
5826
+ }
5827
+ if (keepCurrentAccount && opts.model !== undefined && mechanism !== "resume") {
5828
+ throw new LanesError("model_requires_resume", "--model with --account requires a message that resumes the lane", { lane_id: laneId, state: lane.state });
5829
+ }
5830
+ if (opts.worker !== undefined && mechanism === "dropbox") {
5831
+ throw new LanesError("worker_profile_requires_resume", `Lane ${laneId} is still running; --worker can only be applied when message starts a resumed provider turn`, { lane_id: laneId, state: lane.state });
5832
+ }
5833
+ if (cliEffort !== undefined && mechanism === "dropbox") {
5834
+ throw new LanesError("lane_effort_requires_resume", `Lane ${laneId} is still running; --effort can only be applied when message starts a resumed provider turn`, { lane_id: laneId, state: lane.state });
5835
+ }
5836
+ const resumeProfile = opts.worker !== undefined
5837
+ ? resolveResumeProfile(ctx, lane, opts.worker)
5838
+ : undefined;
5839
+ if (opts.model !== undefined && opts.account === undefined) {
5840
+ throw new LanesError("model_requires_account", "--model on hq lanes message is only accepted together with --account", {});
5841
+ }
5842
+ const automaticAccountSelection = opts.account === undefined && mechanism === "resume"
5843
+ ? await selectAutomaticClaudeResumeAccount(ctx, lane, resumeProfile)
5844
+ : undefined;
5845
+ const resumeEffort = resolveResumeEffort(ctx, lane, {
5846
+ cliEffort,
5847
+ profile: resumeProfile,
5848
+ account: keepCurrentAccount ? lane.account : opts.account ?? automaticAccountSelection?.selection.account,
5849
+ model: opts.model,
5717
5850
  });
5718
- }
5719
- if (move?.queued) {
5720
- // Parked at the target's cap: reconcile resumes the session there.
5721
- }
5722
- else {
5723
- const dispatched = await dispatchLaneDelivery(lane, {
5724
- dropbox: () => {
5725
- // Live (or about-to-be-live) process: the PreToolUse drain delivers it.
5726
- if (!laneAcceptsOperatorMessage(lane)) {
5727
- throw new LanesError("lane_not_accepting_messages", operatorMessageRefusalReason(lane.state), { lane_id: laneId, state: lane.state });
5851
+ const messageId = mintMessageId();
5852
+ const at = ctx.now().toISOString();
5853
+ const projectId = opts.project?.trim() || lane.project_id;
5854
+ const storyId = opts.story?.trim() || lane.story_id;
5855
+ if (opts.story !== undefined && !opts.story.trim()) {
5856
+ throw new LanesError("missing_story", "--story must be non-empty", {});
5857
+ }
5858
+ if ((opts.project !== undefined || opts.story !== undefined) && ctx.storyLedgerEnabled()) {
5859
+ validateProjectStories(hqRoot, projectId, [storyId], lane.company?.slug);
5860
+ }
5861
+ const framedMonitorEvent = opts.monitorEvent === undefined
5862
+ ? undefined
5863
+ : formatMonitorEvent({
5864
+ monitorId: opts.monitorEvent.monitor_id,
5865
+ description: opts.monitorEvent.description,
5866
+ sequence: opts.monitorEvent.sequence,
5867
+ text,
5868
+ });
5869
+ const deliveryText = framedMonitorEvent ?? text;
5870
+ const dropboxMessage = {
5871
+ at,
5872
+ message_id: messageId,
5873
+ text,
5874
+ project_id: projectId,
5875
+ story_id: storyId,
5876
+ ...(opts.monitorEvent === undefined ? {} : {
5877
+ kind: "monitor_event",
5878
+ monitor_id: opts.monitorEvent.monitor_id,
5879
+ description: opts.monitorEvent.description,
5880
+ sequence: opts.monitorEvent.sequence,
5881
+ }),
5882
+ };
5883
+ let resumed = null;
5884
+ let move = null;
5885
+ let queuedOnAdmission = false;
5886
+ if (opts.account !== undefined && !keepCurrentAccount) {
5887
+ // A moved message is always a resume on the target account: a running
5888
+ // lane (the dropbox mechanism) is refused inside prepareAccountMove.
5889
+ move = await prepareAccountMove(ctx, lane, {
5890
+ account: opts.account,
5891
+ cliModel: opts.model?.trim() || undefined,
5892
+ ...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
5893
+ ...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
5894
+ pending: {
5895
+ trigger: "message",
5896
+ text: deliveryText,
5897
+ ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
5898
+ message_id: messageId,
5899
+ compact: opts.compact !== false,
5900
+ },
5901
+ });
5902
+ lane = move.lane;
5903
+ if (move.queued) {
5904
+ archiveResumeMessage(hqRoot, laneId, dropboxMessage);
5905
+ }
5906
+ }
5907
+ else if (mechanism === "resume") {
5908
+ const automatic = automaticAccountSelection;
5909
+ if (automatic !== undefined) {
5910
+ const selection = automatic.selection;
5911
+ if (selection.account !== lane.account) {
5912
+ move = await prepareAccountMove(ctx, lane, {
5913
+ account: selection.account,
5914
+ cliModel: undefined,
5915
+ ...(automatic.historyReason !== undefined ? { accountHistoryReason: automatic.historyReason } : {}),
5916
+ ...(resumeProfile !== undefined ? { workerProfile: resumeProfile } : {}),
5917
+ ...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
5918
+ pending: {
5919
+ trigger: "message",
5920
+ text: deliveryText,
5921
+ ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
5922
+ message_id: messageId,
5923
+ compact: opts.compact !== false,
5924
+ },
5925
+ accountSelection: selection,
5926
+ });
5927
+ lane = move.lane;
5928
+ if (move.queued)
5929
+ archiveResumeMessage(hqRoot, laneId, dropboxMessage);
5728
5930
  }
5729
- appendDropboxMessage(hqRoot, laneId, dropboxMessage);
5730
- return null;
5731
- },
5732
- resume: async () => {
5733
- // Exited worker with a recorded session: queue the message when capacity
5734
- // is occupied, otherwise start its next turn under the shared admission lock.
5735
- assertLaneRunsHereForAdmission(ctx, lane);
5736
- return withLaneAdmissionLock(hqRoot, lane.provider, async () => {
5737
- const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
5738
- if (admission.action === "refuse" && admission.error)
5739
- throw admission.error;
5740
- return withResumeHireAdmission(ctx, lane, () => {
5741
- if (admission.action === "queue") {
5742
- const sessionId = lane.provider_session_id;
5743
- if (!sessionId) {
5744
- throw new LanesError("no_provider_session", `Lane ${laneId} has no provider session id to queue this message resume`, {
5745
- lane_id: laneId,
5746
- provider: lane.provider,
5747
- });
5748
- }
5749
- archiveResumeMessage(hqRoot, laneId, dropboxMessage);
5750
- queuedOnAdmission = true;
5751
- return casUpdateLane(hqRoot, laneId, (current) => {
5752
- if (current.version !== lane.version || current.state !== lane.state) {
5753
- throw new LanesError("lane_admission_conflict", `Lane ${laneId} changed while message admission was checked`, {
5931
+ else {
5932
+ lane = persistClaudeAccountSelection(hqRoot, lane, selection);
5933
+ }
5934
+ }
5935
+ }
5936
+ if (resumeProfile !== undefined && (opts.account === undefined || keepCurrentAccount)) {
5937
+ validateResumeProfileModel(ctx, lane, resumeProfile.profile);
5938
+ }
5939
+ if (move === null && (resumeProfile !== undefined || resumeEffort !== undefined || (keepCurrentAccount && opts.model !== undefined))) {
5940
+ lane = recordResumeSetup(ctx, lane, {
5941
+ ...(resumeProfile !== undefined ? { profile: resumeProfile } : {}),
5942
+ ...(resumeEffort !== undefined ? { effort: resumeEffort } : {}),
5943
+ ...(keepCurrentAccount && opts.model !== undefined ? { cliModel: opts.model } : {}),
5944
+ });
5945
+ }
5946
+ if (move?.queued) {
5947
+ // Parked at the target's cap: reconcile resumes the session there.
5948
+ markAccepted();
5949
+ }
5950
+ else {
5951
+ const dispatched = await dispatchLaneDelivery(lane, {
5952
+ dropbox: () => {
5953
+ // Live (or about-to-be-live) process: the PreToolUse drain delivers it.
5954
+ if (!laneAcceptsOperatorMessage(lane)) {
5955
+ throw new LanesError("lane_not_accepting_messages", operatorMessageRefusalReason(lane.state), { lane_id: laneId, state: lane.state });
5956
+ }
5957
+ appendDropboxMessage(hqRoot, laneId, dropboxMessage);
5958
+ return null;
5959
+ },
5960
+ resume: async () => {
5961
+ // Exited worker with a recorded session: queue the message when capacity
5962
+ // is occupied, otherwise start its next turn under the shared admission lock.
5963
+ assertLaneRunsHereForAdmission(ctx, lane);
5964
+ return withLaneAdmissionLock(hqRoot, lane.provider, async () => {
5965
+ const admission = await assessLaneAdmission(ctx, lane, { queueAt: at });
5966
+ if (admission.action === "refuse" && admission.error)
5967
+ throw admission.error;
5968
+ return withResumeHireAdmission(ctx, lane, () => {
5969
+ if (admission.action === "queue") {
5970
+ const sessionId = lane.provider_session_id;
5971
+ if (!sessionId) {
5972
+ throw new LanesError("no_provider_session", `Lane ${laneId} has no provider session id to queue this message resume`, {
5754
5973
  lane_id: laneId,
5755
- prepared_version: lane.version,
5756
- version: current.version,
5757
- state: current.state,
5974
+ provider: lane.provider,
5758
5975
  });
5759
5976
  }
5760
- return {
5761
- ...current,
5762
- state: "queued",
5763
- queued_reason: admission.reason ?? "throttle",
5764
- queued_at: at,
5765
- pending_resume: {
5766
- session_id: sessionId,
5767
- trigger: "message",
5768
- text: deliveryText,
5769
- ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
5770
- message_id: messageId,
5771
- compact: opts.compact !== false,
5772
- at,
5773
- },
5774
- };
5775
- });
5776
- }
5777
- const started = spawnLaneWorker(hqRoot, lane, {
5778
- env: ctx.env,
5779
- modelDeps: {
5780
- readFileSync: ctx.readFileSync,
5781
- probe: ctx.probeProvider,
5977
+ archiveResumeMessage(hqRoot, laneId, dropboxMessage);
5978
+ queuedOnAdmission = true;
5979
+ return casUpdateLane(hqRoot, laneId, (current) => {
5980
+ if (current.version !== lane.version || current.state !== lane.state) {
5981
+ throw new LanesError("lane_admission_conflict", `Lane ${laneId} changed while message admission was checked`, {
5982
+ lane_id: laneId,
5983
+ prepared_version: lane.version,
5984
+ version: current.version,
5985
+ state: current.state,
5986
+ });
5987
+ }
5988
+ return {
5989
+ ...current,
5990
+ state: "queued",
5991
+ queued_reason: admission.reason ?? "throttle",
5992
+ queued_at: at,
5993
+ pending_resume: {
5994
+ session_id: sessionId,
5995
+ trigger: "message",
5996
+ text: deliveryText,
5997
+ ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
5998
+ message_id: messageId,
5999
+ compact: opts.compact !== false,
6000
+ at,
6001
+ },
6002
+ };
6003
+ });
6004
+ }
6005
+ const started = spawnLaneWorker(hqRoot, lane, {
5782
6006
  env: ctx.env,
5783
- },
5784
- existsSync: ctx.existsSync,
5785
- spawnProcess: ctx.spawnProcess,
5786
- proveDetached: ctx.proveDetached,
5787
- killProcess: ctx.killProcess,
5788
- now: ctx.now,
5789
- resolveMachineIdentity: ctx.resolveMachineIdentity,
5790
- runCompaction: ctx.runCompaction,
5791
- locateTranscript: ctx.locateTranscript,
5792
- }, {
5793
- trigger: {
5794
- kind: "message",
5795
- text: deliveryText,
5796
- message_id: messageId,
5797
- ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
5798
- },
5799
- compact: opts.compact !== false,
5800
- effort: resumeEffort ?? recordedResumeEffort(lane),
6007
+ modelDeps: {
6008
+ readFileSync: ctx.readFileSync,
6009
+ probe: ctx.probeProvider,
6010
+ env: ctx.env,
6011
+ },
6012
+ existsSync: ctx.existsSync,
6013
+ spawnProcess: ctx.spawnProcess,
6014
+ proveDetached: ctx.proveDetached,
6015
+ killProcess: ctx.killProcess,
6016
+ now: ctx.now,
6017
+ resolveMachineIdentity: ctx.resolveMachineIdentity,
6018
+ runCompaction: ctx.runCompaction,
6019
+ locateTranscript: ctx.locateTranscript,
6020
+ }, {
6021
+ trigger: {
6022
+ kind: "message",
6023
+ text: deliveryText,
6024
+ message_id: messageId,
6025
+ ...(opts.monitorEvent === undefined ? {} : { monitor_event: true }),
6026
+ },
6027
+ compact: opts.compact !== false,
6028
+ effort: resumeEffort ?? recordedResumeEffort(lane),
6029
+ });
6030
+ archiveResumeMessage(hqRoot, laneId, dropboxMessage);
6031
+ return started;
5801
6032
  });
5802
- archiveResumeMessage(hqRoot, laneId, dropboxMessage);
5803
- return started;
5804
- });
5805
- }, ctx.sleep);
5806
- },
5807
- }, mechanism);
5808
- if (dispatched.mechanism === "resume")
5809
- resumed = dispatched.value;
5810
- }
5811
- // Every accepted message moves the compaction reference forward.
5812
- let next = casUpdateLane(hqRoot, laneId, (current) => ({
5813
- ...resetKeepAliveProgress(current),
5814
- last_message_at: at,
5815
- }));
5816
- if (projectId !== lane.project_id || storyId !== lane.story_id) {
5817
- next = casUpdateLane(hqRoot, laneId, (current) => {
5818
- const already = current.attribution_history.some((entry) => entry.message_id === messageId);
5819
- if (already)
5820
- return current;
5821
- return {
5822
- ...current,
5823
- project_id: projectId,
5824
- story_id: storyId,
5825
- no_story_reason: storyId ? undefined : current.no_story_reason,
5826
- stories: laneStory(current, projectId, storyId)
5827
- ? laneStories(current)
5828
- : [
5829
- ...laneStories(current),
5830
- {
5831
- project_id: projectId,
5832
- story_id: storyId,
5833
- status: "in_progress",
5834
- added_at: at,
5835
- updated_at: at,
5836
- },
5837
- ],
5838
- attribution_history: appendCappedHistory(current.attribution_history, {
6033
+ }, ctx.sleep);
6034
+ },
6035
+ }, mechanism);
6036
+ if (dispatched.mechanism === "resume")
6037
+ resumed = dispatched.value;
6038
+ markAccepted();
6039
+ }
6040
+ // Every accepted message moves the compaction reference forward.
6041
+ let next = casUpdateLane(hqRoot, laneId, (current) => ({
6042
+ ...resetKeepAliveProgress(current),
6043
+ last_message_at: at,
6044
+ }));
6045
+ if (projectId !== lane.project_id || storyId !== lane.story_id) {
6046
+ next = casUpdateLane(hqRoot, laneId, (current) => {
6047
+ const already = current.attribution_history.some((entry) => entry.message_id === messageId);
6048
+ if (already)
6049
+ return current;
6050
+ return {
6051
+ ...current,
5839
6052
  project_id: projectId,
5840
6053
  story_id: storyId,
5841
- message_id: messageId,
5842
- at,
5843
- }),
5844
- };
6054
+ no_story_reason: storyId ? undefined : current.no_story_reason,
6055
+ stories: laneStory(current, projectId, storyId)
6056
+ ? laneStories(current)
6057
+ : [
6058
+ ...laneStories(current),
6059
+ {
6060
+ project_id: projectId,
6061
+ story_id: storyId,
6062
+ status: "in_progress",
6063
+ added_at: at,
6064
+ updated_at: at,
6065
+ },
6066
+ ],
6067
+ attribution_history: appendCappedHistory(current.attribution_history, {
6068
+ project_id: projectId,
6069
+ story_id: storyId,
6070
+ message_id: messageId,
6071
+ at,
6072
+ }),
6073
+ };
6074
+ });
6075
+ }
6076
+ // What this command can claim when it returns: the message is durably
6077
+ // written (accepted). Delivery to the model is asynchronous in both
6078
+ // mechanisms. `dropbox` delivers on the worker's next tool call, and only
6079
+ // if the drain hook is registered in the account home the worker was
6080
+ // started from; `resume` has started a new worker with the message as its
6081
+ // prompt. Neither is "delivered", so the response never says so.
6082
+ const drainHook = drainHookStatusFor(ctx, next);
6083
+ const deliveryStatus = move?.queued
6084
+ ? "queued_on_target_account"
6085
+ : queuedOnAdmission
6086
+ ? "queued_on_admission"
6087
+ : mechanism === "dropbox"
6088
+ ? "awaiting_next_tool_call"
6089
+ : "worker_spawned";
6090
+ const warning = mechanism === "dropbox" && drainHook !== null && !drainHook.registered
6091
+ ? `The drain hook is not registered in ${drainHook.path}; the running worker will not receive this message. It stays in the inbox and is delivered on the lane's next start.`
6092
+ : mechanism === "dropbox" && next.stalled === true
6093
+ ? `The worker has made no tool call since ${next.stalled_at ?? "an unknown time"} (${next.stalled_reason ?? "stalled"}); delivery waits on its next tool call.`
6094
+ : null;
6095
+ if (!opts.silent)
6096
+ printJson({
6097
+ ok: true,
6098
+ action: "message",
6099
+ lane_id: laneId,
6100
+ message_id: messageId,
6101
+ accepted: true,
6102
+ delivered: false,
6103
+ delivery: mechanism,
6104
+ delivery_status: deliveryStatus,
6105
+ inbox_path: mechanism === "dropbox" ? dropboxInboxPath(hqRoot, laneId) : null,
6106
+ drain_hook: drainHook,
6107
+ warning,
6108
+ resumed_session_id: resumed?.last_resume?.session_id ?? null,
6109
+ compaction: resumed?.last_compaction && resumed.last_compaction.resume_at === resumed.last_resume?.at
6110
+ ? resumed.last_compaction
6111
+ : null,
6112
+ account_move: move ? accountMoveSummary(move) : null,
6113
+ project_id: next.project_id,
6114
+ story_id: next.story_id,
6115
+ stories: laneStories(next),
6116
+ story_warning: storyWarning(next) ?? null,
6117
+ lane: next,
6118
+ });
6119
+ }
6120
+ catch (err) {
6121
+ if (claimedMessages.length > 0 && !accepted) {
6122
+ const error = err instanceof Error ? err.message : String(err);
6123
+ writeWatcherDecision(watcherOutLogPath(hqRoot, laneId), {
6124
+ event: "message_recovery_failed",
6125
+ at: ctx.now().toISOString(),
6126
+ state: lane.state,
6127
+ error,
6128
+ message_count: claimedMessages.length,
6129
+ }, WATCHER_OUT_MAX_BYTES);
6130
+ try {
6131
+ requeueDropboxMessages(hqRoot, laneId, claimedMessages);
6132
+ }
6133
+ catch (requeueErr) {
6134
+ const requeueMessage = requeueErr instanceof Error ? requeueErr.message : String(requeueErr);
6135
+ writeWatcherDecision(watcherOutLogPath(hqRoot, laneId), {
6136
+ event: "message_requeue_failed",
6137
+ at: ctx.now().toISOString(),
6138
+ state: lane.state,
6139
+ recovery_error: error,
6140
+ requeue_error: requeueMessage,
6141
+ message_count: claimedMessages.length,
6142
+ }, WATCHER_OUT_MAX_BYTES);
6143
+ throw new LanesError("message_requeue_failed", `Could not return ${claimedMessages.length} queued lane message(s) to the inbox after recovery failed: ${requeueMessage}`, { lane_id: laneId, message_count: claimedMessages.length, recovery_error: error });
6144
+ }
6145
+ }
6146
+ throw err;
6147
+ }
6148
+ }
6149
+ function messageLaneExclusion(ctx, lane) {
6150
+ if (lane.state === "killed" || lane.state === "cancelled")
6151
+ return lane.state;
6152
+ return keepAliveExclusion(lane, loadAllLanes(ctx.hqRoot()), loadRetiredLaneIds(ctx.hqRoot()));
6153
+ }
6154
+ function recordMessageUndeliverable(ctx, lane, reason, messageCount) {
6155
+ writeWatcherDecision(watcherOutLogPath(ctx.hqRoot(), lane.lane_id), {
6156
+ event: "message_undeliverable",
6157
+ at: ctx.now().toISOString(),
6158
+ state: lane.state,
6159
+ reason,
6160
+ ...(messageCount === undefined ? {} : { message_count: messageCount }),
6161
+ }, WATCHER_OUT_MAX_BYTES);
6162
+ }
6163
+ /**
6164
+ * Watcher and reconcile call the hidden command below after they win the
6165
+ * terminal transition. The inbox is claimed once with the normal atomic
6166
+ * dropbox drain, and an accepted message is routed through runMessage so the
6167
+ * existing provider, admission, account, worker-profile and senior checks
6168
+ * remain authoritative.
6169
+ */
6170
+ async function recoverQueuedMessages(ctx, laneId) {
6171
+ const hqRoot = ctx.hqRoot();
6172
+ const lane = readLane(hqRoot, laneId);
6173
+ if (!dropboxInboxHasMessages(hqRoot, laneId))
6174
+ return { action: "none", message_count: 0 };
6175
+ const exclusion = messageLaneExclusion(ctx, lane);
6176
+ const refusal = exclusion ?? (lane.state === "done"
6177
+ ? "done_before_envelope"
6178
+ : lane.state === "awaiting_input"
6179
+ ? "awaiting_input_requires_question_answer"
6180
+ : laneDeliveryMechanism(lane) !== "resume"
6181
+ ? `no_resume_path_for_${lane.state}`
6182
+ : undefined);
6183
+ if (refusal !== undefined) {
6184
+ const messages = drainDropbox(hqRoot, laneId, { archiveDirectory: "undeliverable" });
6185
+ if (messages.length > 0)
6186
+ recordMessageUndeliverable(ctx, lane, refusal, messages.length);
6187
+ return { action: messages.length > 0 ? "undeliverable" : "none", message_count: messages.length };
6188
+ }
6189
+ const messages = drainDropbox(hqRoot, laneId);
6190
+ if (messages.length === 0)
6191
+ return { action: "none", message_count: 0 };
6192
+ let accepted = false;
6193
+ try {
6194
+ await runMessage(ctx, laneId, {
6195
+ text: formatDropboxMessages(messages),
6196
+ compact: false,
6197
+ silent: true,
6198
+ onAccepted: () => { accepted = true; },
5845
6199
  });
6200
+ return { action: "resumed", message_count: messages.length };
6201
+ }
6202
+ catch (err) {
6203
+ const error = err instanceof Error ? err.message : String(err);
6204
+ writeWatcherDecision(watcherOutLogPath(hqRoot, laneId), {
6205
+ event: "message_recovery_failed",
6206
+ at: ctx.now().toISOString(),
6207
+ state: lane.state,
6208
+ error,
6209
+ message_count: messages.length,
6210
+ }, WATCHER_OUT_MAX_BYTES);
6211
+ if (!accepted) {
6212
+ try {
6213
+ requeueDropboxMessages(hqRoot, laneId, messages);
6214
+ }
6215
+ catch (requeueErr) {
6216
+ const requeueMessage = requeueErr instanceof Error ? requeueErr.message : String(requeueErr);
6217
+ writeWatcherDecision(watcherOutLogPath(hqRoot, laneId), {
6218
+ event: "message_requeue_failed",
6219
+ at: ctx.now().toISOString(),
6220
+ state: lane.state,
6221
+ recovery_error: error,
6222
+ requeue_error: requeueMessage,
6223
+ message_count: messages.length,
6224
+ }, WATCHER_OUT_MAX_BYTES);
6225
+ throw new LanesError("message_requeue_failed", `Could not return ${messages.length} queued lane message(s) to the inbox after recovery failed: ${requeueMessage}`, { lane_id: laneId, message_count: messages.length, recovery_error: error });
6226
+ }
6227
+ }
6228
+ throw err;
5846
6229
  }
5847
- // What this command can claim when it returns: the message is durably
5848
- // written (accepted). Delivery to the model is asynchronous in both
5849
- // mechanisms. `dropbox` delivers on the worker's next tool call, and only
5850
- // if the drain hook is registered in the account home the worker was
5851
- // started from; `resume` has started a new worker with the message as its
5852
- // prompt. Neither is "delivered", so the response never says so.
5853
- const drainHook = drainHookStatusFor(ctx, next);
5854
- const deliveryStatus = move?.queued
5855
- ? "queued_on_target_account"
5856
- : queuedOnAdmission
5857
- ? "queued_on_admission"
5858
- : mechanism === "dropbox"
5859
- ? "awaiting_next_tool_call"
5860
- : "worker_spawned";
5861
- const warning = mechanism === "dropbox" && drainHook !== null && !drainHook.registered
5862
- ? `The drain hook is not registered in ${drainHook.path}; the running worker will not receive this message. It stays in the inbox and is delivered on the lane's next start.`
5863
- : mechanism === "dropbox" && next.stalled === true
5864
- ? `The worker has made no tool call since ${next.stalled_at ?? "an unknown time"} (${next.stalled_reason ?? "stalled"}); delivery waits on its next tool call.`
5865
- : null;
5866
- printJson({
5867
- ok: true,
5868
- action: "message",
5869
- lane_id: laneId,
5870
- message_id: messageId,
5871
- accepted: true,
5872
- delivered: false,
5873
- delivery: mechanism,
5874
- delivery_status: deliveryStatus,
5875
- inbox_path: mechanism === "dropbox" ? dropboxInboxPath(hqRoot, laneId) : null,
5876
- drain_hook: drainHook,
5877
- warning,
5878
- resumed_session_id: resumed?.last_resume?.session_id ?? null,
5879
- compaction: resumed?.last_compaction && resumed.last_compaction.resume_at === resumed.last_resume?.at
5880
- ? resumed.last_compaction
5881
- : null,
5882
- account_move: move ? accountMoveSummary(move) : null,
5883
- project_id: next.project_id,
5884
- story_id: next.story_id,
5885
- stories: laneStories(next),
5886
- story_warning: storyWarning(next) ?? null,
5887
- lane: next,
5888
- });
5889
6230
  }
5890
6231
  function accountMoveSummary(move) {
5891
6232
  return {
@@ -7219,7 +7560,7 @@ async function runAsk(ctx, opts) {
7219
7560
  const attempts = ASK_ATTEMPTS;
7220
7561
  for (let i = 0; i < attempts; i += 1) {
7221
7562
  try {
7222
- const withBody = raiseLaneQuestion({
7563
+ let withBody = raiseLaneQuestion({
7223
7564
  hqRoot,
7224
7565
  lane,
7225
7566
  body,
@@ -7229,6 +7570,9 @@ async function runAsk(ctx, opts) {
7229
7570
  questionId,
7230
7571
  });
7231
7572
  questionId = withBody.question_id;
7573
+ if (await ctx.laneTrackerMeshEnabled(questionCompany)) {
7574
+ withBody = await indexTrackerQuestion(ctx, hqRoot, lane, withBody, body, questionCompany);
7575
+ }
7232
7576
  await emitLaneMeshEvent(ctx, lane.mesh_company ?? lane.company, lane.thread_id, "lane_question", {
7233
7577
  lane_id: lane.lane_id,
7234
7578
  question_id: withBody.question_id,
@@ -7615,18 +7959,28 @@ async function runQuestionsList(ctx, opts) {
7615
7959
  const outpostLoad = opts.allHosts
7616
7960
  ? await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot, company)
7617
7961
  : { questions: [], errors: [] };
7962
+ const meshRows = await ctx.laneTrackerMeshEnabled(company) && status !== "expired"
7963
+ ? await createTrackerMeshQuestionClient({ company, request: ctx.trackerMeshRequest }).list(status ? [status] : undefined)
7964
+ : [];
7618
7965
  for (const owner of outpostLoad.questions) {
7619
7966
  assertQuestionCompanyAccess(owner.question, owner.lane, company, hqRoot);
7620
7967
  syncPrimaryQuestionDelivery(hqRoot, owner);
7621
7968
  }
7622
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)));
7623
7971
  const questions = [
7624
7972
  ...localQuestions
7625
7973
  .filter((q) => !remoteQuestionIds.has(q.question_id))
7626
7974
  .map((q) => {
7975
+ const meshRow = q.mesh_question_id ? meshRows.find((row) => row.questionId === q.mesh_question_id) : undefined;
7627
7976
  const row = {
7628
7977
  ...q,
7629
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
+ } : {}),
7630
7984
  ...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
7631
7985
  };
7632
7986
  if (!opts.allHosts)
@@ -7658,6 +8012,50 @@ async function runQuestionsList(ctx, opts) {
7658
8012
  audience,
7659
8013
  ...(q.question_kind === "continue" ? { flag: "continue-loop" } : {}),
7660
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
+ })),
7661
8059
  ];
7662
8060
  printJson({
7663
8061
  ok: true,
@@ -7675,6 +8073,43 @@ async function runQuestionsShow(ctx, questionId) {
7675
8073
  catch (err) {
7676
8074
  if (!isLanesError(err) || err.error !== "question_not_found")
7677
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
+ }
7678
8113
  const lookup = await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot);
7679
8114
  if (lookup.errors.length > 0) {
7680
8115
  throw new LanesError("outpost_question_lookup_unavailable", `Could not verify whether Outposts own question ${questionId}`, {
@@ -7730,6 +8165,42 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
7730
8165
  if (!isLanesError(err) || err.error !== "question_not_found")
7731
8166
  throw err;
7732
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
+ }
7733
8204
  const lookup = await loadPrimaryOwnedOutpostQuestions(ctx, hqRoot, company);
7734
8205
  if (lookup.errors.length > 0) {
7735
8206
  throw new LanesError("outpost_question_lookup_unavailable", `Could not verify whether Outposts own question ${questionId}`, {
@@ -7805,6 +8276,22 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
7805
8276
  throw new LanesError("missing_choice", "--choice or --freeform is required", {});
7806
8277
  }
7807
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
+ }
7808
8295
  const at = ctx.now().toISOString();
7809
8296
  const answeredBy = sessionIdFromEnv(ctx.env)
7810
8297
  ? `session:${sessionIdFromEnv(ctx.env)}`
@@ -7839,6 +8326,7 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
7839
8326
  answered_at: at,
7840
8327
  answered_level: current.hop,
7841
8328
  },
8329
+ ...(meshQuestionId ? { mesh_question_id: meshQuestionId } : {}),
7842
8330
  };
7843
8331
  });
7844
8332
  const latestAskingLane = readLane(hqRoot, askingLane.lane_id);
@@ -7873,6 +8361,8 @@ async function runQuestionsAnswer(ctx, questionId, opts) {
7873
8361
  if (wake.status === "failed") {
7874
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 });
7875
8363
  }
8364
+ if (remoteAnswer)
8365
+ await syncTrackerMeshQuestions(ctx, hqRoot);
7876
8366
  printJson({
7877
8367
  ok: true,
7878
8368
  action: "questions.answer",
@@ -8532,6 +9022,10 @@ async function runReconcile(ctx, opts) {
8532
9022
  livenessDeps: livenessDepsFromCtx(ctx),
8533
9023
  presenceConnected: ctx.presenceConnected,
8534
9024
  resolveQuestionActiveCompany: () => resolveActiveQuestionCompany(ctx),
9025
+ syncTrackerQuestions: (root) => syncTrackerMeshQuestions(ctx, root),
9026
+ recoverQueuedMessages: async (lane) => {
9027
+ await recoverQueuedMessages(ctx, lane.lane_id);
9028
+ },
8535
9029
  mesh: ctx.mesh,
8536
9030
  env: ctx.env,
8537
9031
  });
@@ -8609,6 +9103,7 @@ export function registerLanesCommand(parent, deps = {}) {
8609
9103
  resolveCompany: ctx.resolveCompany,
8610
9104
  trackerEnabled: ctx.laneTrackerEnabled,
8611
9105
  trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
9106
+ meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
8612
9107
  now: ctx.now,
8613
9108
  actor: ctx.trackerActor,
8614
9109
  }, wrap);
@@ -8617,6 +9112,7 @@ export function registerLanesCommand(parent, deps = {}) {
8617
9112
  resolveCompany: ctx.resolveCompany,
8618
9113
  trackerEnabled: ctx.laneTrackerEnabled,
8619
9114
  trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
9115
+ meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
8620
9116
  now: ctx.now,
8621
9117
  }, wrap);
8622
9118
  registerLaneChecksCommand(lanes, {
@@ -8624,6 +9120,7 @@ export function registerLanesCommand(parent, deps = {}) {
8624
9120
  resolveCompany: ctx.resolveCompany,
8625
9121
  trackerEnabled: ctx.laneTrackerEnabled,
8626
9122
  trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
9123
+ meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
8627
9124
  now: ctx.now,
8628
9125
  }, wrap);
8629
9126
  registerLaneReportCommands(lanes, {
@@ -8631,6 +9128,7 @@ export function registerLanesCommand(parent, deps = {}) {
8631
9128
  resolveCompany: ctx.resolveCompany,
8632
9129
  trackerEnabled: ctx.laneTrackerEnabled,
8633
9130
  trackerMeshEnabled: ctx.laneTrackerMeshEnabled,
9131
+ meshStore: (company, projectId) => createWorkMeshTrackerStore({ company, projectId, request: ctx.trackerMeshRequest, now: ctx.now }),
8634
9132
  now: ctx.now,
8635
9133
  readQuestionBody: (root, question) => {
8636
9134
  const body = requireQuestionBody(root, question);
@@ -8863,6 +9361,12 @@ export function registerLanesCommand(parent, deps = {}) {
8863
9361
  .action(wrap(async (laneId, opts) => {
8864
9362
  await runMessage(ctx, laneId, opts);
8865
9363
  }));
9364
+ lanes
9365
+ .command("_recover-queued-messages", { hidden: true })
9366
+ .argument("<lane-id>", "Lane id")
9367
+ .action(wrap(async (laneId) => {
9368
+ printJson(await recoverQueuedMessages(ctx, laneId));
9369
+ }));
8866
9370
  lanes
8867
9371
  .command("_monitor-message", { hidden: true })
8868
9372
  .argument("<lane-id>", "Lane id")