paseo-follow-up 1.2.0-beta.3 → 1.2.0-beta.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -101,7 +101,7 @@ The content comes from a new `needs_you` tool that every root agent gets while t
101
101
  Install the beta:
102
102
 
103
103
  ```sh
104
- paseo plugin update follow-through --version 1.2.0-beta.3
104
+ paseo plugin update follow-through --version 1.2.0-beta.4
105
105
  # or, for a fresh install:
106
106
  paseo plugin install npm:paseo-follow-up@beta
107
107
  ```
@@ -117,7 +117,7 @@ Betas publish to npm's `beta` dist-tag, so `paseo plugin update` without `--vers
117
117
  ## How it works
118
118
 
119
119
  - The workspace's main agent owns its PRs, tasks, questions, the spec link and the Done call. Other agents in the workspace can register PRs and check status.
120
- - **Auto-send** is on for every workspace tracked for the first time (1.2.0-beta.3), so review threads and red CI go to the agent without you. Workspaces tracked before keep their setting. Turn it off per workspace and each repair batch waits for your click; with it on, Follow-up sends batches itself once the agent is idle, after fresh permission and Session Guard checks.
120
+ - **Auto-send** is on for every workspace tracked for the first time (1.2.0-beta.4), so review threads and red CI go to the agent without you. Workspaces tracked before keep their setting. Turn it off per workspace and each repair batch waits for your click; with it on, Follow-up sends batches itself once the agent is idle, after fresh permission and Session Guard checks.
121
121
  - Follow-up never merges, turns on merge when ready, requests reviewers or archives on its own. **Merge** re-checks the stack, lists any PRs underneath that would merge first, and asks again.
122
122
  - A merged PR is not the end: tasks such as production checks and open questions keep the workspace open until the main agent declares Done and everything has settled.
123
123
  - Answers go out only when you click Send. Follow-up never answers for you.
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "paseo-follow-up",
3
3
  "private": false,
4
- "version": "1.2.0-beta.3",
4
+ "version": "1.2.0-beta.4",
5
5
  "description": "Follow every workspace PR stack in Paseo, answer agent questions, batch repairs for the main agent, and see what needs you.",
6
6
  "type": "module",
7
7
  "files": [
package/server/daemon.ts CHANGED
@@ -137,9 +137,11 @@ export class PaseoGateway implements Gateway {
137
137
  const candidates = agents.map(row => row.agent).filter(a => a.workspaceId === workspace.id && !a.archivedAt && !a.labels["follow-through.recovery"]);
138
138
  const roots: Agent[] = [];
139
139
  for (const agent of candidates) if (await isWorkspaceRoot(agent, findParent)) roots.push(agent);
140
- const main = chooseMainAgent(roots, existing?.mainAgentId);
140
+ const successor = successorMainAgent(roots, existing?.mainAgentId);
141
+ const main = successor ?? chooseMainAgent(roots, existing?.mainAgentId);
141
142
  if (!main && !existing) continue;
142
- const row: DiscoveredWorkspace = { id: workspace.id, title: workspace.name || main?.title || existing!.title, mainAgentId: existing?.mainAgentId ?? main!.id, projectId: workspace.projectId, messageAt: main?.lastUserMessageAt ?? null, enable_default_tracking: defaults.has(workspace.projectId ?? ""), contributorAgentIds: roots.filter(a => a.id !== existing?.mainAgentId && a.id !== main?.id).map(a => a.id) };
143
+ const owner = successor?.id ?? existing?.mainAgentId ?? main!.id;
144
+ const row: DiscoveredWorkspace = { id: workspace.id, title: workspace.name || main?.title || existing!.title, mainAgentId: owner, ...(successor ? { replacesMainAgentId: existing!.mainAgentId } : {}), projectId: workspace.projectId, messageAt: main?.lastUserMessageAt ?? null, enable_default_tracking: defaults.has(workspace.projectId ?? ""), contributorAgentIds: roots.filter(a => a.id !== owner && a.id !== existing?.mainAgentId && a.id !== main?.id).map(a => a.id) };
143
145
  // Preserve the designated owner even when it is no longer indexed. Other root
144
146
  // sessions can still contribute their PRs without acquiring completion authority.
145
147
  const project = catalog.projects.find(p => p.projectId === workspace.projectId);
@@ -364,3 +366,15 @@ export function chooseMainAgent(roots: Agent[], existingId?: string): Agent | un
364
366
  if (existingId) return roots.find(a => a.id === existingId);
365
367
  return [...roots].sort((a, b) => a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id))[0];
366
368
  }
369
+
370
+ /**
371
+ * The recorded main agent is closed, archived or gone while the workspace still has a live root agent: the live
372
+ * one takes over (the one Tom wrote to last, then the newest). A live recorded main is never replaced.
373
+ */
374
+ export function successorMainAgent(roots: Agent[], existingId: string | undefined): Agent | undefined {
375
+ if (!existingId) return undefined;
376
+ const current = roots.find(a => a.id === existingId);
377
+ if (current && current.status !== "closed") return undefined;
378
+ const live = roots.filter(a => a.id !== existingId && a.status !== "closed");
379
+ return live.sort((a, b) => (b.lastUserMessageAt ?? "").localeCompare(a.lastUserMessageAt ?? "") || b.createdAt.localeCompare(a.createdAt) || a.id.localeCompare(b.id))[0];
380
+ }
@@ -4,7 +4,7 @@ import { mergeArtifacts } from "./spec";
4
4
  import type { State, Workspace } from "../shared/model";
5
5
 
6
6
  export type History = { candidates: string[]; goal: string | null; complete: boolean; error: string | null };
7
- export type DiscoveredWorkspace = { id: string; title: string; mainAgentId: string; messageAt: string | null; projectId?: string | null; enable_default_tracking?: boolean; history?: History; contributorAgentIds?: string[]; submissions?: Submission[]; artifacts?: Artifact[] };
7
+ export type DiscoveredWorkspace = { id: string; title: string; mainAgentId: string; /** Set when mainAgentId is a live successor to this closed or gone main agent. */ replacesMainAgentId?: string; messageAt: string | null; projectId?: string | null; enable_default_tracking?: boolean; history?: History; contributorAgentIds?: string[]; submissions?: Submission[]; artifacts?: Artifact[] };
8
8
 
9
9
  export function extractHistory(items: readonly unknown[], complete = true, error: string | null = null): History {
10
10
  const candidates = new Set<string>();
@@ -42,7 +42,12 @@ export function applyDiscovery(state: State, discovered: DiscoveredWorkspace[],
42
42
  state.workspaces.push(w);
43
43
  }
44
44
  w.title = row.title;
45
- // An unavailable original owner is surfaced instead of silently transferring its authority.
45
+ // A closed or gone main agent hands the workspace to its live successor (discovery decides; see
46
+ // successorMainAgent). Any other mismatch keeps the recorded owner.
47
+ if (row.replacesMainAgentId && row.replacesMainAgentId === w.mainAgentId && !w.archived) {
48
+ w.mainAgentId = row.mainAgentId; w.runtimeReason = null;
49
+ for (const b of w.batches) if (b.state === "pending" && b.recipientAgentId === row.replacesMainAgentId) b.recipientAgentId = null;
50
+ }
46
51
  if (w.mainAgentId !== row.mainAgentId || w.archived) continue;
47
52
  if (row.contributorAgentIds) w.contributorAgentIds = row.contributorAgentIds;
48
53
  if (row.enable_default_tracking && !w.enable_tracking && (!row.projectId || state.projectDefaults?.[row.projectId] !== false)) { w.enable_auto_sends = firstTracking(w); w.enable_tracking = true; }