baa-conductor

git clone 

commit
ca554cc
parent
7982f5e
author
codex@macbookpro
date
2026-03-31 17:40:28 +0800 CST
fix: surface detailed projector route skip reasons
2 files changed,  +227, -24
M apps/conductor-daemon/src/index.test.js
+179, -15
  1@@ -39,6 +39,7 @@ import {
  2   parseBaaInstructionBlock,
  3   parseConductorCliRequest,
  4   routeBaaInstruction,
  5+  shouldRenew,
  6   writeHttpResponse
  7 } from "../dist/index.js";
  8 import { observeRenewalConversation } from "../dist/renewal/conversations.js";
  9@@ -3061,24 +3062,87 @@ test("renewal projector scans settled messages with cursor semantics and skips i
 10 
 11     await artifactStore.upsertLocalConversation({
 12       automationStatus: "auto",
 13-      localConversationId: "lc_missing_target",
 14+      localConversationId: "lc_non_proxy_target",
 15       platform: "claude"
 16     });
 17     await artifactStore.upsertConversationLink({
 18-      clientId: "firefox-missing-target",
 19-      linkId: "link_missing_target",
 20-      localConversationId: "lc_missing_target",
 21+      clientId: "firefox-non-proxy-target",
 22+      linkId: "link_non_proxy_target",
 23+      localConversationId: "lc_non_proxy_target",
 24       observedAt: nowMs - 5_000,
 25-      pageUrl: "https://claude.ai/chat/conv_missing_target",
 26+      pageUrl: "https://claude.ai/chat/conv_non_proxy_target",
 27       platform: "claude",
 28-      remoteConversationId: "conv_missing_target"
 29+      remoteConversationId: "conv_non_proxy_target",
 30+      targetId: "tab:5",
 31+      targetKind: "browser.shell_page",
 32+      targetPayload: {
 33+        clientId: "firefox-non-proxy-target",
 34+        tabId: 5
 35+      }
 36     });
 37-    const missingTargetMessage = await artifactStore.insertMessage({
 38-      conversationId: "conv_missing_target",
 39-      id: "msg_missing_target",
 40+    const nonProxyTargetMessage = await artifactStore.insertMessage({
 41+      conversationId: "conv_non_proxy_target",
 42+      id: "msg_non_proxy_target",
 43       observedAt: nowMs - 5_000,
 44       platform: "claude",
 45-      rawText: "missing target should not project",
 46+      rawText: "non proxy delivery target should not project",
 47+      role: "assistant"
 48+    });
 49+
 50+    await artifactStore.upsertLocalConversation({
 51+      automationStatus: "auto",
 52+      localConversationId: "lc_shell_page",
 53+      platform: "claude"
 54+    });
 55+    await artifactStore.upsertConversationLink({
 56+      clientId: "firefox-shell-page",
 57+      linkId: "link_shell_page",
 58+      localConversationId: "lc_shell_page",
 59+      observedAt: nowMs - 4_000,
 60+      pageUrl: "https://claude.ai/chat/conv_shell_page",
 61+      platform: "claude",
 62+      remoteConversationId: "conv_shell_page",
 63+      targetId: "tab:6",
 64+      targetKind: "browser.proxy_delivery",
 65+      targetPayload: {
 66+        clientId: "firefox-shell-page",
 67+        shellPage: true,
 68+        tabId: 6
 69+      }
 70+    });
 71+    const shellPageMessage = await artifactStore.insertMessage({
 72+      conversationId: "conv_shell_page",
 73+      id: "msg_shell_page",
 74+      observedAt: nowMs - 4_000,
 75+      platform: "claude",
 76+      rawText: "shell page route should not project",
 77+      role: "assistant"
 78+    });
 79+
 80+    await artifactStore.upsertLocalConversation({
 81+      automationStatus: "auto",
 82+      localConversationId: "lc_missing_tab_id",
 83+      platform: "claude"
 84+    });
 85+    await artifactStore.upsertConversationLink({
 86+      clientId: "firefox-missing-tab-id",
 87+      linkId: "link_missing_tab_id",
 88+      localConversationId: "lc_missing_tab_id",
 89+      observedAt: nowMs - 3_000,
 90+      pageUrl: "https://claude.ai/chat/conv_missing_tab_id",
 91+      platform: "claude",
 92+      remoteConversationId: "conv_missing_tab_id",
 93+      targetKind: "browser.proxy_delivery",
 94+      targetPayload: {
 95+        clientId: "firefox-missing-tab-id"
 96+      }
 97+    });
 98+    const missingTabIdMessage = await artifactStore.insertMessage({
 99+      conversationId: "conv_missing_tab_id",
100+      id: "msg_missing_tab_id",
101+      observedAt: nowMs - 3_000,
102+      platform: "claude",
103+      rawText: "missing tab id should not project",
104       role: "assistant"
105     });
106 
107@@ -3136,8 +3200,8 @@ test("renewal projector scans settled messages with cursor semantics and skips i
108     const cursorState = await repository.getSystemState("renewal.projector.cursor");
109     assert.ok(cursorState);
110     assert.deepEqual(JSON.parse(cursorState.valueJson), {
111-      message_id: missingTargetMessage.id,
112-      observed_at: missingTargetMessage.observedAt
113+      message_id: missingTabIdMessage.id,
114+      observed_at: missingTabIdMessage.observedAt
115     });
116 
117     const entries = readJsonlEntries(logsDir);
118@@ -3157,9 +3221,28 @@ test("renewal projector scans settled messages with cursor semantics and skips i
119         (entry) => entry.runner === "renewal.projector" && entry.stage === "message_skipped" && entry.result === "cooldown_active"
120       )
121     );
122+    const routeUnavailableEntries = entries.filter(
123+      (entry) => entry.runner === "renewal.projector" && entry.stage === "message_skipped" && entry.result === "route_unavailable"
124+    );
125     assert.ok(
126-      entries.find(
127-        (entry) => entry.runner === "renewal.projector" && entry.stage === "message_skipped" && entry.result === "route_unavailable"
128+      routeUnavailableEntries.find(
129+        (entry) =>
130+          entry.message_id === nonProxyTargetMessage.id
131+          && entry.route_unavailable_reason === "target_kind_not_proxy_delivery"
132+      )
133+    );
134+    assert.ok(
135+      routeUnavailableEntries.find(
136+        (entry) =>
137+          entry.message_id === shellPageMessage.id
138+          && entry.route_unavailable_reason === "shell_page"
139+      )
140+    );
141+    assert.ok(
142+      routeUnavailableEntries.find(
143+        (entry) =>
144+          entry.message_id === missingTabIdMessage.id
145+          && entry.route_unavailable_reason === "missing_tab_id"
146       )
147     );
148     assert.ok(
149@@ -3167,7 +3250,7 @@ test("renewal projector scans settled messages with cursor semantics and skips i
150         (entry) =>
151           entry.runner === "renewal.projector"
152           && entry.stage === "scan_completed"
153-          && entry.cursor_after === `message:${missingTargetMessage.observedAt}:${missingTargetMessage.id}`
154+          && entry.cursor_after === `message:${missingTabIdMessage.observedAt}:${missingTabIdMessage.id}`
155       )
156     );
157 
158@@ -3188,6 +3271,87 @@ test("renewal projector scans settled messages with cursor semantics and skips i
159   }
160 });
161 
162+test("shouldRenew keeps route_unavailable while exposing structured route failure details", async () => {
163+  const nowMs = Date.UTC(2026, 2, 30, 10, 5, 0);
164+  const baseCandidate = {
165+    conversation: {
166+      automationStatus: "auto",
167+      cooldownUntil: null
168+    },
169+    link: {
170+      isActive: true,
171+      targetId: "tab:42",
172+      targetKind: "browser.proxy_delivery",
173+      targetPayload: JSON.stringify({
174+        clientId: "firefox-route-detail",
175+        tabId: 42
176+      })
177+    },
178+    message: {
179+      id: "msg_route_detail"
180+    }
181+  };
182+
183+  const cases = [
184+    {
185+      expectedReason: "inactive_link",
186+      link: {
187+        isActive: false
188+      }
189+    },
190+    {
191+      expectedReason: "target_kind_not_proxy_delivery",
192+      link: {
193+        targetKind: "browser.shell_page"
194+      }
195+    },
196+    {
197+      expectedReason: "shell_page",
198+      link: {
199+        targetPayload: JSON.stringify({
200+          clientId: "firefox-route-detail",
201+          shellPage: true,
202+          tabId: 42
203+        })
204+      }
205+    },
206+    {
207+      expectedReason: "missing_tab_id",
208+      link: {
209+        targetId: null,
210+        targetPayload: JSON.stringify({
211+          clientId: "firefox-route-detail"
212+        })
213+      }
214+    }
215+  ];
216+
217+  for (const [index, testCase] of cases.entries()) {
218+    const decision = await shouldRenew({
219+      candidate: {
220+        ...baseCandidate,
221+        link: {
222+          ...baseCandidate.link,
223+          ...testCase.link
224+        },
225+        message: {
226+          id: `${baseCandidate.message.id}_${index}`
227+        }
228+      },
229+      now: nowMs,
230+      store: {
231+        async listRenewalJobs() {
232+          assert.fail("route-unavailable branches should short-circuit before duplicate-job lookup");
233+        }
234+      }
235+    });
236+
237+    assert.equal(decision.eligible, false);
238+    assert.equal(decision.reason, "route_unavailable");
239+    assert.equal(decision.routeUnavailableReason, testCase.expectedReason);
240+  }
241+});
242+
243 test("renewal dispatcher sends due pending jobs through browser.proxy_delivery and marks them done", async () => {
244   const rootDir = mkdtempSync(join(tmpdir(), "baa-renewal-dispatcher-success-"));
245   const stateDir = join(rootDir, "state");
M apps/conductor-daemon/src/renewal/projector.ts
+48, -9
  1@@ -29,6 +29,12 @@ export type RenewalProjectorSkipReason =
  2   | "missing_remote_conversation_id"
  3   | "route_unavailable";
  4 
  5+export type RenewalRouteUnavailableReason =
  6+  | "inactive_link"
  7+  | "missing_tab_id"
  8+  | "shell_page"
  9+  | "target_kind_not_proxy_delivery";
 10+
 11 export interface RenewalProjectorCursor extends MessageScanCursor {}
 12 
 13 export interface RenewalProjectorPayload {
 14@@ -70,6 +76,7 @@ export interface RenewalShouldRenewDecision {
 15   eligible: boolean;
 16   existingJobId?: string | null;
 17   reason: "eligible" | RenewalProjectorSkipReason;
 18+  routeUnavailableReason?: RenewalRouteUnavailableReason;
 19 }
 20 
 21 export interface RenewalProjectorRunnerOptions {
 22@@ -94,6 +101,11 @@ interface CursorStateValue {
 23   observed_at: number;
 24 }
 25 
 26+interface RenewalRouteAvailabilityResult {
 27+  available: boolean;
 28+  reason: RenewalRouteUnavailableReason | null;
 29+}
 30+
 31 export function createRenewalProjectorRunner(
 32   options: RenewalProjectorRunnerOptions
 33 ): TimedJobRunner {
 34@@ -218,7 +230,12 @@ export async function runRenewalProjector(
 35           cursor: formatCursor(cursorAfter),
 36           existing_job_id: decision.existingJobId ?? null,
 37           local_conversation_id: resolution.candidate.conversation.localConversationId,
 38-          message_id: message.id
 39+          message_id: message.id,
 40+          ...(decision.routeUnavailableReason == null
 41+            ? {}
 42+            : {
 43+                route_unavailable_reason: decision.routeUnavailableReason
 44+              })
 45         }
 46       });
 47       continue;
 48@@ -330,10 +347,13 @@ export async function shouldRenew(input: {
 49     };
 50   }
 51 
 52-  if (!hasAvailableRoute(candidate.link)) {
 53+  const routeAvailability = hasAvailableRoute(candidate.link);
 54+
 55+  if (!routeAvailability.available) {
 56     return {
 57       eligible: false,
 58-      reason: "route_unavailable"
 59+      reason: "route_unavailable",
 60+      routeUnavailableReason: routeAvailability.reason ?? undefined
 61     };
 62   }
 63 
 64@@ -488,13 +508,19 @@ function formatCursor(cursor: RenewalProjectorCursor | null): string | null {
 65   return `message:${cursor.observedAt}:${cursor.id}`;
 66 }
 67 
 68-function hasAvailableRoute(link: ConversationLinkRecord): boolean {
 69+function hasAvailableRoute(link: ConversationLinkRecord): RenewalRouteAvailabilityResult {
 70   if (link.isActive !== true) {
 71-    return false;
 72+    return {
 73+      available: false,
 74+      reason: "inactive_link"
 75+    };
 76   }
 77 
 78   if (normalizeOptionalString(link.targetKind) !== "browser.proxy_delivery") {
 79-    return false;
 80+    return {
 81+      available: false,
 82+      reason: "target_kind_not_proxy_delivery"
 83+    };
 84   }
 85 
 86   const targetPayload = parseJsonRecord(link.targetPayload);
 87@@ -504,11 +530,24 @@ function hasAvailableRoute(link: ConversationLinkRecord): boolean {
 88       ? targetPayload.tabId
 89       : parseTargetTabId(link.targetId);
 90 
 91-  if (shellPage || tabId == null) {
 92-    return false;
 93+  if (shellPage) {
 94+    return {
 95+      available: false,
 96+      reason: "shell_page"
 97+    };
 98   }
 99 
100-  return true;
101+  if (tabId == null) {
102+    return {
103+      available: false,
104+      reason: "missing_tab_id"
105+    };
106+  }
107+
108+  return {
109+    available: true,
110+    reason: null
111+  };
112 }
113 
114 function isCursorStateValue(value: unknown): value is CursorStateValue {