@hyperdrive.bot/fleet-server 0.3.153 → 0.3.155

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.
@@ -50,6 +50,20 @@ export interface TmuxWrap {
50
50
  session: string;
51
51
  }
52
52
  export declare function tmuxEnabled(env?: NodeJS.ProcessEnv): boolean;
53
+ /**
54
+ * Env the pane must get from THIS agent, never from the tmux server.
55
+ *
56
+ * The server is started once, by whichever agent spawns first, and keeps that agent's
57
+ * environment for its whole life; every pane is the server's child and inherits it. So a
58
+ * value that differs per agent silently becomes the first agent's value everywhere
59
+ * (AWPROD-6933: half the live sessions carried one agent's PASEO_AGENT_ID, and `/cleanup` in
60
+ * any of them archived that agent). `new-session -e` puts the value on the session itself,
61
+ * which tmux layers over the server's global environment.
62
+ *
63
+ * An allowlist, not "all of env": these land in argv, which `ps` shows to every process on
64
+ * the host, so nothing secret belongs here. PASEO_AGENT_ID is an id, not a credential.
65
+ */
66
+ export declare const TMUX_PER_AGENT_ENV: readonly ["PASEO_AGENT_ID"];
53
67
  /** Stable, collision-free session name. */
54
68
  export declare function tmuxSessionName(sessionId: string): string;
55
69
  /**
@@ -75,6 +75,20 @@ function writeConf(dir, dims) {
75
75
  ].join("\n") + "\n", { mode: 0o600 });
76
76
  return conf;
77
77
  }
78
+ /**
79
+ * Env the pane must get from THIS agent, never from the tmux server.
80
+ *
81
+ * The server is started once, by whichever agent spawns first, and keeps that agent's
82
+ * environment for its whole life; every pane is the server's child and inherits it. So a
83
+ * value that differs per agent silently becomes the first agent's value everywhere
84
+ * (AWPROD-6933: half the live sessions carried one agent's PASEO_AGENT_ID, and `/cleanup` in
85
+ * any of them archived that agent). `new-session -e` puts the value on the session itself,
86
+ * which tmux layers over the server's global environment.
87
+ *
88
+ * An allowlist, not "all of env": these land in argv, which `ps` shows to every process on
89
+ * the host, so nothing secret belongs here. PASEO_AGENT_ID is an id, not a credential.
90
+ */
91
+ export const TMUX_PER_AGENT_ENV = ["PASEO_AGENT_ID"];
78
92
  /** Stable, collision-free session name. */
79
93
  export function tmuxSessionName(sessionId) {
80
94
  return `paseo-${sessionId.replace(/[^A-Za-z0-9_-]/g, "").slice(0, 40)}`;
@@ -96,6 +110,10 @@ export function wrapWithTmux(opts) {
96
110
  const socket = path.join(dir, "paseo.sock");
97
111
  const conf = writeConf(dir, opts.dims);
98
112
  const session = tmuxSessionName(opts.sessionId);
113
+ const perAgentEnv = TMUX_PER_AGENT_ENV.flatMap((key) => {
114
+ const value = opts.env[key];
115
+ return value ? ["-e", `${key}=${value}`] : [];
116
+ });
99
117
  return {
100
118
  binary: "tmux",
101
119
  args: [
@@ -113,6 +131,7 @@ export function wrapWithTmux(opts) {
113
131
  String(opts.dims.cols),
114
132
  "-y",
115
133
  String(opts.dims.rows),
134
+ ...perAgentEnv,
116
135
  "--",
117
136
  opts.binary,
118
137
  ...opts.args,
@@ -1606,14 +1606,30 @@ export function createPaseoToolCatalog(options) {
1606
1606
  });
1607
1607
  registerTool("archive_agent", {
1608
1608
  title: "Archive agent",
1609
- description: "Archive an agent (soft-delete). The agent is interrupted if running and removed from the active list.",
1609
+ description: "Archive an agent (soft-delete). The agent is interrupted if running and removed from the active list. " +
1610
+ "When archiving YOURSELF (end of session), pass expectSelf: true: the call is then refused unless " +
1611
+ "agentId is the agent this MCP connection belongs to, so a stale or inherited PASEO_AGENT_ID can " +
1612
+ "never archive someone else's session.",
1610
1613
  inputSchema: {
1611
1614
  agentId: z.string(),
1615
+ expectSelf: z.boolean().optional(),
1612
1616
  },
1613
1617
  outputSchema: {
1614
1618
  success: z.boolean(),
1615
1619
  },
1616
- }, async ({ agentId }) => {
1620
+ }, async ({ agentId, expectSelf }) => {
1621
+ // AWPROD-6933: PASEO_AGENT_ID reached agents through a shared tmux server and named the
1622
+ // FIRST agent that server was started for, so a self-archive in any other session
1623
+ // archived that one instead. callerAgentId comes from this connection's own url, which
1624
+ // is the value nothing inherits.
1625
+ if (expectSelf && agentId !== callerAgentId) {
1626
+ throw new Error(callerAgentId
1627
+ ? `Refusing to archive ${agentId}: expectSelf is set but this session is agent ` +
1628
+ `${callerAgentId}. Your PASEO_AGENT_ID is probably inherited from another agent; ` +
1629
+ `nothing was archived.`
1630
+ : `Refusing to archive ${agentId}: expectSelf is set but this connection has no caller ` +
1631
+ `agent to compare against; nothing was archived.`);
1632
+ }
1617
1633
  await archiveAgentCommand({
1618
1634
  agentManager,
1619
1635
  agentStorage,