@afokapu/atdd-flow 0.3.49 → 0.3.50

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.
@@ -318,6 +318,28 @@ export async function awaitLaunchActivation({ root, seat, piSession, herdrSessio
318
318
  throw new Error("Timed out waiting for verified Flow launch receipt and binding.");
319
319
  }
320
320
 
321
+ /**
322
+ * Host-attached Pi has no launcher variables; it may activate only when Herdr's injected pane identity exactly
323
+ * matches the Desk seat's native scoped binding. This reads the seat and never writes or creates a binding.
324
+ */
325
+ export async function hostAttachedActivation({ root, seat, herdrSession, pane, piSession, piSessionPath }: { root: string; seat: string; herdrSession?: string; pane?: string; piSession?: string; piSessionPath?: string }) {
326
+ if (!herdrSession || !pane) return false;
327
+ try {
328
+ const runtime = (await readYaml<Seat>(paths(root).seatFile(seat))).runtime;
329
+ return nativeScopedTo(runtime, herdrSession, pane) && sessionUnsetOrEqual(runtime, piSession, piSessionPath);
330
+ } catch { return false; }
331
+ }
332
+
333
+ function nativeScopedTo(runtime: Seat["runtime"], herdrSession: string, pane: string) {
334
+ const locator = runtime?.addresses.herdr;
335
+ return runtime?.application === "herdr" && runtime.wake === "native" && !runtime.launch_receipt
336
+ && typeof locator === "object" && locator.session === herdrSession && locator.pane === pane;
337
+ }
338
+
339
+ const unsetOrEqual = (recorded: string | undefined, live: string | undefined) => !recorded || recorded === live;
340
+ const sessionUnsetOrEqual = (runtime: Seat["runtime"], piSession?: string, piSessionPath?: string) =>
341
+ unsetOrEqual(runtime?.pi_session, piSession) && unsetOrEqual(runtime?.pi_session_path, piSessionPath);
342
+
321
343
  /** Injects the current task-derived action; failure to derive never blocks mail or startup. */
322
344
  async function sendTaskDirective(pi: ExtensionAPI, root: string, seat: string) {
323
345
  try {
@@ -337,6 +359,8 @@ export default function (pi: ExtensionAPI) {
337
359
  if (!root || !seat) return;
338
360
  let runtime: ReturnType<typeof createPiRuntime> | undefined;
339
361
  let handoverPrompted = false;
362
+ // Host-attach only: mail older than this activation is consumed without a prompt, so a long backlog cannot trigger one turn per message.
363
+ let hostActivatedAt: number | undefined;
340
364
  pi.on("turn_end", async (_event, ctx) => {
341
365
  if (handoverPrompted) return;
342
366
  const percent = ctx.getContextUsage()?.percent;
@@ -369,6 +393,7 @@ export default function (pi: ExtensionAPI) {
369
393
  pi.on("session_start", async (_event, ctx) => {
370
394
  runtime = createPiRuntime({ root, seat, deferActivation: true, deliver: async (mail, thread, path, sendIfOwned) => {
371
395
  if (!sendIfOwned) return false;
396
+ if (hostActivatedAt !== undefined && Date.parse(typeof mail.created_at === "string" ? mail.created_at : "") < hostActivatedAt) return sendIfOwned(async () => undefined);
372
397
  return sendIfOwned(async () => {
373
398
  // Task state is re-read at every wake, so queued mail cannot carry a stale action.
374
399
  await sendTaskDirective(pi, root, seat);
@@ -383,6 +408,14 @@ export default function (pi: ExtensionAPI) {
383
408
  const piSession = process.env.ATDD_FLOW_PI_SESSION;
384
409
  const herdrSession = process.env.ATDD_FLOW_HERDR_SESSION;
385
410
  const pane = process.env.ATDD_FLOW_HERDR_PANE;
411
+ if (!piSession && !herdrSession && !pane) {
412
+ const sessionManager = (ctx as { sessionManager?: { getSessionId?: () => string; getSessionFile?: () => string | undefined } }).sessionManager;
413
+ if (await hostAttachedActivation({ root, seat, herdrSession: process.env.HERDR_SESSION, pane: process.env.HERDR_PANE_ID, piSession: sessionManager?.getSessionId?.(), piSessionPath: sessionManager?.getSessionFile?.() })) {
414
+ hostActivatedAt = Date.now();
415
+ if (await runtime.activate()) pi.sendMessage({ customType: "atdd-flow-start", content: `Native mail active for ${seat} (host-attached). Notices created before this session were not replayed; run atdd-flow status seat ${seat} for unanswered mail.`, display: true, details: { seat, root } }, { triggerTurn: true, deliverAs: "followUp" });
416
+ }
417
+ return;
418
+ }
386
419
  if (!piSession || !herdrSession || !pane) return;
387
420
  const current = runtime;
388
421
  void awaitLaunchActivation({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@afokapu/atdd-flow",
3
- "version": "0.3.49",
3
+ "version": "0.3.50",
4
4
  "description": "Filesystem-first Desk registry for projects, seats, tasks, threads, and handoffs.",
5
5
  "type": "module",
6
6
  "bin": {