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 +1 -1
- package/README.md +12 -2
- package/index.server.ts +12 -10
- package/package.json +3 -3
- package/paseo-plugin.json +1 -1
- package/server/session-mcp.ts +15 -0
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.
|
|
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
|
[](https://www.npmjs.com/package/paseo-follow-up)
|
|
4
4
|
[](https://www.npmjs.com/package/paseo-follow-up)
|
|
5
|
-
[](https://paseo.sh)
|
|
6
6
|
[](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.
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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.
|
|
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.
|
|
32
|
-
"@getpaseo/plugin": "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
|
@@ -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
|
+
}
|