paseo-smart-session 3.5.0 → 3.6.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.
@@ -1,26 +1,63 @@
1
- import type { PluginServerContext } from "@getpaseo/plugin/server";
1
+ import type { PluginServerContext, PluginSessionOpenRequest } from "@getpaseo/plugin/server";
2
+ import { randomUUID } from "node:crypto";
3
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
4
+ import { readFile, writeFile } from "node:fs/promises";
2
5
  import { homedir } from "node:os";
3
6
  import { join } from "node:path";
4
7
  import { mcpDefinition, resolvePluginDir } from "./install.ts";
8
+ import { type Invoke, nativeTools, nativeToolsAvailable } from "./native-tools.ts";
5
9
  import { readSettings } from "./settings.ts";
6
10
 
7
- /** MCP is a launch contribution, so it never writes global provider settings. */
8
- export function contributeSessionMcp(server: PluginServerContext) {
11
+ const paseoHome = () => process.env.PASEO_HOME ?? join(homedir(), ".paseo");
12
+
13
+ async function paseoConfig(): Promise<Record<string, any> | null> {
14
+ try { return JSON.parse(await readFile(join(paseoHome(), "config.json"), "utf8")); } catch { return null; }
15
+ }
16
+
17
+ /** Providers whose sessions get the tools. A test harness may widen it to its fixture provider. */
18
+ const providers = () => (process.env.SMART_SESSION_TOOL_PROVIDERS ?? "claude,codex").split(",").map((name) => name.trim());
19
+
20
+ /**
21
+ * The agent tools are a launch contribution, so nothing global is written. On Paseo 0.15+ they go
22
+ * on `request.tools` and the daemon serves them through the agent's own Paseo MCP; otherwise this
23
+ * launch alone gets `mcp.mjs` as a stdio server. `SMART_SESSION_TOOLS` tells the hooks which names
24
+ * the agent has (`smart_checkpoint` or `checkpoint`).
25
+ */
26
+ export function contributeSessionMcp(server: PluginServerContext, invoke: Invoke) {
27
+ // A reload or update detaches native tools from open sessions until each reopens. This worker's instance, and the
28
+ // instance that attached each session, let the hooks tell a detached session how to reach the tools meanwhile
29
+ // (hooks/context.mjs toolsMode). Written synchronously, before any session can open.
30
+ const instance = randomUUID();
31
+ const data = join(paseoHome(), "plugin-data", "smart-session");
32
+ try {
33
+ mkdirSync(join(data, "tools"), { recursive: true });
34
+ writeFileSync(join(data, `tools-instance.json.${instance}`), JSON.stringify({ instance }));
35
+ renameSync(join(data, `tools-instance.json.${instance}`), join(data, "tools-instance.json"));
36
+ } catch { /* Without it the hooks assume the tools are attached. */ }
9
37
  return server.before("agent.session_open", async ({ request }) => {
10
- if (request.purpose !== "interactive" || !["claude", "codex"].includes(request.provider)
38
+ if (request.purpose !== "interactive" || !providers().includes(request.provider)
11
39
  || request.labels["plugin-tools/enabled"] === "false" || !(await readSettings()).enabled) return;
12
- const directory = await resolvePluginDir();
13
- if (!directory) return;
14
- const definition = mcpDefinition(directory);
15
- return {
16
- ...request,
17
- mcpServers: { ...request.mcpServers, "smart-session": { ...definition, env: {
18
- ...definition.env, PASEO_AGENT_ID: request.agentId,
19
- PASEO_HOME: process.env.PASEO_HOME ?? join(homedir(), ".paseo"), SMART_SESSION_MCP_SCOPE: "paseo",
20
- ...(process.env.PASEO_DAEMON_URL ? { PASEO_DAEMON_URL: process.env.PASEO_DAEMON_URL } : {}),
21
- ...(process.env.PASEO_PASSWORD ? { PASEO_PASSWORD: process.env.PASEO_PASSWORD } : {}),
22
- ...(process.env.PASEO_PASSWORD_FILE ? { PASEO_PASSWORD_FILE: process.env.PASEO_PASSWORD_FILE } : {}),
23
- } } },
24
- };
40
+ if (process.env.SMART_SESSION_FORCE_STDIO !== "1" && nativeToolsAvailable(request, await paseoConfig())) {
41
+ await writeFile(join(data, "tools", `native-${request.agentId.replace(/[^A-Za-z0-9_-]/g, "_")}.json`), JSON.stringify({ instance })).catch(() => undefined);
42
+ return { ...request, env: { ...request.env, SMART_SESSION_TOOLS: "native" }, tools: [...request.tools, ...nativeTools(invoke)] };
43
+ }
44
+ return withStdio(request);
25
45
  });
26
46
  }
47
+
48
+ async function withStdio(request: PluginSessionOpenRequest): Promise<PluginSessionOpenRequest | void> {
49
+ const directory = await resolvePluginDir();
50
+ if (!directory) return;
51
+ const definition = mcpDefinition(directory);
52
+ return {
53
+ ...request,
54
+ env: { ...request.env, SMART_SESSION_TOOLS: "stdio" },
55
+ mcpServers: { ...request.mcpServers, "smart-session": { ...definition, env: {
56
+ ...definition.env, PASEO_AGENT_ID: request.agentId,
57
+ PASEO_HOME: paseoHome(), SMART_SESSION_MCP_SCOPE: "paseo",
58
+ ...(process.env.PASEO_DAEMON_URL ? { PASEO_DAEMON_URL: process.env.PASEO_DAEMON_URL } : {}),
59
+ ...(process.env.PASEO_PASSWORD ? { PASEO_PASSWORD: process.env.PASEO_PASSWORD } : {}),
60
+ ...(process.env.PASEO_PASSWORD_FILE ? { PASEO_PASSWORD_FILE: process.env.PASEO_PASSWORD_FILE } : {}),
61
+ } } },
62
+ };
63
+ }
package/shared/chat.ts CHANGED
@@ -31,9 +31,13 @@ export const PromptCardSchema = z.object({
31
31
  });
32
32
  export type PromptCard = z.infer<typeof PromptCardSchema>;
33
33
 
34
- /** Claude records mcp__smart-session__context_status; Codex records smart-session.context_status. */
34
+ /**
35
+ * Claude records mcp__smart-session__context_status and Codex smart-session.context_status from the
36
+ * stdio server; served natively through Paseo's MCP they are mcp__paseo__smart_context_status and
37
+ * paseo.smart_context_status.
38
+ */
35
39
  export function smartSessionTool(name: string): SmartSessionTool | null {
36
- const match = /^(?:mcp__)?smart-session(?:__|\.|\/|:)([a-z_]+)$/.exec(name);
40
+ const match = /^(?:mcp__)?(?:smart-session(?:__|\.|\/|:)|paseo(?:__|\.|\/|:)smart_)([a-z_]+)$/.exec(name);
37
41
  return match && (SMART_SESSION_TOOLS as readonly string[]).includes(match[1]!) ? (match[1] as SmartSessionTool) : null;
38
42
  }
39
43