paseo-follow-up 0.1.2 → 0.2.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.
package/OVERVIEW.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Follow-up keeps PR stacks, remaining tasks and explicit agent completion attached to a Paseo workspace. It groups workspaces by whether you or an agent can act next and retains settled PR history.
4
4
 
5
- Requires Paseo 0.11+, Node 22+ and authenticated gh on the daemon host. Graphite CLI authentication is used for readiness and explicit Merge. The optional Send to Paseo browser companion enables Graphite's Merge when ready setting; it is not needed for tracking, recovery or follow-up.
5
+ Requires Paseo 0.14+, Node 22+ and authenticated gh on the daemon host. Graphite CLI authentication is used for readiness and explicit Merge. The optional Send to Paseo browser companion enables Graphite's Merge when ready setting; it is not needed for tracking, recovery or follow-up.
6
6
 
7
7
  Open the sidebar or Command-K, then Track this workspace. Project settings can enroll existing and new workspaces with original main-agent ownership; Auto always starts off. The retained development default enables canonical Git repositories named hero-poc, while other projects require opt-in. Disable that project default to stop further enrollment without losing records.
8
8
 
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![npm version](https://img.shields.io/npm/v/paseo-follow-up?style=for-the-badge&color=cb3837)](https://www.npmjs.com/package/paseo-follow-up)
4
4
  [![npm downloads](https://img.shields.io/npm/dm/paseo-follow-up?style=for-the-badge&color=cb3837)](https://www.npmjs.com/package/paseo-follow-up)
5
- [![Paseo](https://img.shields.io/badge/Paseo-%E2%89%A5%200.11.0-8A63D2?style=for-the-badge)](https://paseo.sh)
5
+ [![Paseo](https://img.shields.io/badge/Paseo-%E2%89%A5%200.14.0-8A63D2?style=for-the-badge)](https://paseo.sh)
6
6
  [![License](https://img.shields.io/github/license/tomgrin10/paseo-follow-up?style=for-the-badge&color=2563eb)](LICENSE)
7
7
 
8
8
  Keep long-lived Paseo workspaces moving through review, deployment, and completion. Follow-up retains PR stacks and remaining tasks, batches repairs for the existing main agent, and records the agent's explicit Done declaration.
@@ -13,7 +13,7 @@ Screenshots use synthetic workspaces and PRs.
13
13
 
14
14
  ## Requirements
15
15
 
16
- - Paseo 0.11.0 or newer and Node 22 or newer.
16
+ - Paseo 0.14.0 or newer and Node 22 or newer.
17
17
  - Authenticated GitHub CLI (`gh`) on the daemon host.
18
18
  - Saved Graphite CLI authentication for Graphite readiness and explicit Merge controls.
19
19
  - Optional Send to Paseo browser companion for Graphite's actual Merge when ready switch. Core tracking and follow-up work without it.
@@ -63,6 +63,16 @@ Integration checks use a separate Paseo home, synthetic agents and fixtures. Nev
63
63
 
64
64
  Release tags (`vX.Y.Z`) run `.github/workflows/publish-npm.yml`. GitHub Actions verifies the plugin and packed installation, publishes through npm Trusted Publishing, verifies registry integrity, then creates the GitHub release. No npm token is stored in repository secrets.
65
65
 
66
+ ## Paseo 0.14 agent integration
67
+
68
+ The Follow-up MCP attaches when a tracked main or active recovery agent opens a Claude or Codex session. Normal startup no longer writes global Claude/Codex MCP settings. Existing global entries are preserved. Turn-ended discovery now fetches recent history through the SDK because Paseo 0.13+ sends an empty timeline in lifecycle events.
69
+
70
+ Requires Paseo **0.14.0 or newer**. Agent tools are scoped to interactive Claude
71
+ and Codex sessions; history-only opens receive none. They become available on
72
+ the next idle session open after install or reload. The agent label
73
+ `plugin-tools/enabled: "false"` opts a session out. Existing plugin data and
74
+ settings are retained.
75
+
66
76
  ## Other plugins
67
77
 
68
78
  Also available from [Tom Gringauz](https://github.com/tomgrin10):
package/index.server.ts CHANGED
@@ -18,7 +18,7 @@ import { applyHistory, applyDiscovery, extractHistory } from "./server/discovery
18
18
  import { lifecycleOnly, recoveryBatch } from "./shared/follow-up";
19
19
  import { Deployments } from "./server/deployments";
20
20
  import { AgentHooks } from "./server/agent-hooks";
21
- import { AgentMcp } from "./server/agent-mcp";
21
+ import { withSessionMcp } from "./server/session-mcp";
22
22
  import { updateObligation } from "./server/obligations";
23
23
 
24
24
  export default function contribute(server: PluginServerContext) {
@@ -27,7 +27,6 @@ export default function contribute(server: PluginServerContext) {
27
27
  const bridge = join(directory, "mcp.mjs");
28
28
  const store = new Store(directory);
29
29
  const agentHooks = new AgentHooks(directory, clientEntry());
30
- const agentMcp = new AgentMcp(directory, clientEntry(), paseoHome);
31
30
  const gateway = new PaseoGateway();
32
31
  const graphite = new Graphite();
33
32
  const github = new Github(undefined, graphite, new Deployments());
@@ -48,8 +47,6 @@ export default function contribute(server: PluginServerContext) {
48
47
  await engine.recover();
49
48
  hooksStarted = true;
50
49
  await agentHooks.start().catch(() => { hookError = "Automatic sidebar instructions could not be registered for every provider. Check Claude/Codex hook settings; tracking is still available."; });
51
- const mcp = await agentMcp.start();
52
- if ([mcp.claude, mcp.codex].some(status => ["unavailable", "name-conflict"].includes(status))) hookError = "Some agent MCP connections could not be registered. Existing CLI access remains available.";
53
50
  hookTimer = setInterval(() => void agentHooks.heartbeat().catch(() => {}), 30_000);
54
51
  })().catch(() => { loadError = "Follow-through could not start. Check its saved state and worker lock."; });
55
52
  const requireReady = async () => { await ready; if (loadError || stopped) throw new Error(loadError ?? "Follow-through is stopping"); };
@@ -252,9 +249,9 @@ export default function contribute(server: PluginServerContext) {
252
249
  }),
253
250
  server.before("agent.session_open", async ({ request }) => {
254
251
  await ready;
255
- if (loadError || stopped) return request;
252
+ if (loadError || stopped || request.purpose !== "interactive") return request;
256
253
  const saved = await store.snapshot();
257
- let w = saved.workspaces.find(w => w.enable_tracking && (w.mainAgentId === request.agentId || w.batches.some(b => b.recipientAgentId === request.agentId)));
254
+ let w = saved.workspaces.find(w => w.enable_tracking && !w.archived && (w.mainAgentId === request.agentId || w.batches.some(b => b.recipientAgentId === request.agentId && !b.settledAt && b.tasks.every(t => t.kind === "inventory") && ["sending", "sent", "uncertain", "blocked"].includes(b.state))));
258
255
  if (!w && request.workspaceId && !saved.workspaces.some(w => w.id === request.workspaceId && (w.archived || w.mainAgentId !== request.agentId))) {
259
256
  const project = await gateway.projectForWorkspace(saved, request.workspaceId).catch(() => null);
260
257
  if (project?.enable_default_tracking) {
@@ -267,9 +264,10 @@ export default function contribute(server: PluginServerContext) {
267
264
  }
268
265
  }
269
266
  if (!w) return request;
270
- return { ...request, env: { ...request.env, FOLLOW_THROUGH_CLI: bridge, FOLLOW_THROUGH_CLIENT_ENTRY: clientEntry(), ...(w.mainAgentId !== request.agentId ? { FOLLOW_THROUGH_RECOVERY: "1" } : {}) } };
267
+ const recovery = w.mainAgentId !== request.agentId;
268
+ return withSessionMcp({ ...request, env: { ...request.env, FOLLOW_THROUGH_CLI: bridge, FOLLOW_THROUGH_CLIENT_ENTRY: clientEntry(), ...(recovery ? { FOLLOW_THROUGH_RECOVERY: "1" } : {}) } }, directory, clientEntry(), paseoHome, recovery);
271
269
  }),
272
- server.on("agent.turn_ended", async event => {
270
+ server.on("agent.turn_ended", async (event, { paseo, signal }) => {
273
271
  await ready;
274
272
  if (stopped || loadError) return;
275
273
  const interruption = event.outcome.kind === "canceled" || event.outcome.kind === "failed" ? { turnId: event.turnId, at: new Date().toISOString(), kind: event.outcome.kind } : null;
@@ -281,9 +279,13 @@ export default function contribute(server: PluginServerContext) {
281
279
  }
282
280
  if (!(await store.snapshot()).workspaces.some(w => w.enable_tracking && w.mainAgentId === event.agent.id)) return;
283
281
  gateway.markDirty(event.agent.id);
284
- await store.mutate(state => {
282
+ // Since Paseo 0.13 the lifecycle payload deliberately contains no history.
283
+ // Read a bounded tail through the plugin-owned API, outside the state lock.
284
+ const history = await paseo.agents.ref(event.agent.id).timeline.refetch({ direction: "tail", limit: 100, projection: "canonical" }).catch(() => null);
285
+ if (signal.aborted || stopped) return;
286
+ if (history) await store.mutate(state => {
285
287
  const w = state.workspaces.find(w => w.enable_tracking && w.mainAgentId === event.agent.id);
286
- if (w) applyHistory(w, extractHistory(event.timeline), null);
288
+ if (w) applyHistory(w, extractHistory(history.entries.map(entry => entry.item), w.inventory?.historyComplete ?? false, w.inventory?.historyError ?? null), null);
287
289
  });
288
290
  const guarded = interruption && await sessionGuardOwnsInterruption(event.agent.id, interruption).catch(() => false);
289
291
  if (["canceled", "failed"].includes(event.outcome.kind) && !guarded) await store.mutate(state => {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "paseo-follow-up",
3
3
  "private": false,
4
- "version": "0.1.2",
4
+ "version": "0.2.0",
5
5
  "description": "Track workspace pull requests, queue follow-up work, and verify completion in Paseo.",
6
6
  "type": "module",
7
7
  "files": [
@@ -28,8 +28,8 @@
28
28
  "prepare": "node scripts/build.mjs"
29
29
  },
30
30
  "devDependencies": {
31
- "@getpaseo/client": "0.11.0",
32
- "@getpaseo/plugin": "0.11.0",
31
+ "@getpaseo/client": "0.14.0",
32
+ "@getpaseo/plugin": "0.14.0",
33
33
  "@tanstack/react-query": "^5.90.11",
34
34
  "@types/node": "^22.10.2",
35
35
  "@types/react": "~19.2.0",
package/paseo-plugin.json CHANGED
@@ -7,6 +7,6 @@
7
7
  "docs/screenshots/follow-up-overview.png"
8
8
  ],
9
9
  "requirements": {
10
- "paseo": ">=0.11.0"
10
+ "paseo": ">=0.14.0"
11
11
  }
12
12
  }
@@ -0,0 +1,15 @@
1
+ import type { PluginSessionOpenRequest } from "@getpaseo/plugin/server";
2
+ import { mcpDefinition } from "./agent-mcp";
3
+
4
+ /** The bridge and caller identity belong to this launch, never global CLI config. */
5
+ export function withSessionMcp(request: PluginSessionOpenRequest, directory: string, clientEntry: string, home: string, recovery: boolean, env: NodeJS.ProcessEnv = process.env): PluginSessionOpenRequest {
6
+ if (request.purpose !== "interactive" || !["claude", "codex"].includes(request.provider) || request.labels["plugin-tools/enabled"] === "false") return request;
7
+ const definition = mcpDefinition(directory, clientEntry, home);
8
+ return { ...request, mcpServers: { ...request.mcpServers, "follow-through": { ...definition, env: {
9
+ ...definition.env, PASEO_AGENT_ID: request.agentId,
10
+ ...(recovery ? { FOLLOW_THROUGH_RECOVERY: "1" } : {}),
11
+ ...(env.PASEO_DAEMON_URL ? { PASEO_DAEMON_URL: env.PASEO_DAEMON_URL } : {}),
12
+ ...(env.PASEO_PASSWORD ? { PASEO_PASSWORD: env.PASEO_PASSWORD } : {}),
13
+ ...(env.PASEO_PASSWORD_FILE ? { PASEO_PASSWORD_FILE: env.PASEO_PASSWORD_FILE } : {}),
14
+ } } } };
15
+ }