agent-repl 0.1.0 → 0.1.2

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/CHANGELOG.md CHANGED
@@ -4,6 +4,26 @@ All notable changes to `agent-repl` are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
+ ## [0.1.2] — 2026-10-19
8
+
9
+ ### Changed
10
+ - Generated from pi-repl 0.8.3.
11
+
12
+ ### Fixed
13
+ - agent-repl works when the locale is not UTF-8, for example with `LANG` unset. Commands and tools failed with "no panes found", because tmux printed the tabs in its pane listings as `_`.
14
+
15
+ ## [0.1.1] — 2026-10-19
16
+
17
+ ### Changed
18
+ - Generated from pi-repl 0.8.2.
19
+
20
+ ### Fixed
21
+ - `agent-repl stop` no longer refuses to run on macOS when a process of `nobody` (uid -2) is running.
22
+ - IPython shows an expression's value once, as `Out[N]: …`. It used to be printed again as a bare `repr`.
23
+
24
+ ### Documentation
25
+ - Set `PI_REPL_AGENT_LABEL` in the OpenCode configuration example: OpenCode 2 identifies itself to MCP servers only as `cli`.
26
+
7
27
  ## [0.1.0] — 2026-09-25
8
28
 
9
29
  First public release.
package/README.md CHANGED
@@ -73,7 +73,12 @@ codex mcp add agent-repl -- agent-repl mcp
73
73
  ```json
74
74
  {
75
75
  "mcp": {
76
- "agent-repl": { "type": "local", "command": ["agent-repl", "mcp"], "enabled": true }
76
+ "agent-repl": {
77
+ "type": "local",
78
+ "command": ["agent-repl", "mcp"],
79
+ "environment": { "PI_REPL_AGENT_LABEL": "OpenCode" },
80
+ "enabled": true
81
+ }
77
82
  }
78
83
  }
79
84
  ```
@@ -125,7 +130,7 @@ An agent can also pass `echoMode` for a single `repl_send`. Full mode keeps long
125
130
 
126
131
  ## Agent names in the record
127
132
 
128
- Entries in the clean record, and in `agent-repl export`, are labelled with the name the agent gives when it connects: `Claude Code`, `Codex` or `OpenCode`, or the raw client name for other agents. To choose the label yourself, set `PI_REPL_AGENT_LABEL` in the server's environment, as for the echo setting. Submissions from Pi are labelled `Pi`.
133
+ Entries in the clean record, and in `agent-repl export`, are labelled with the name the agent gives when it connects: `Claude Code` and `Codex` name themselves, and other agents are labelled with whatever name they send. To choose the label yourself, set `PI_REPL_AGENT_LABEL` in the server's environment, as for the echo setting. OpenCode 2 identifies itself only as `cli`, so the OpenCode configuration above sets the label. Submissions from Pi are labelled `Pi`.
129
134
 
130
135
  ## Privacy and safety
131
136
 
package/dist/pi-repl.js CHANGED
@@ -1,4 +1,4 @@
1
- // GENERATED by scripts/sync-from-pi-repl.mjs from pi-repl 0.8.1 (0926b0b). Do not edit by hand:
1
+ // GENERATED by scripts/sync-from-pi-repl.mjs from pi-repl 0.8.3 (45b5042). Do not edit by hand:
2
2
  // change pi-repl and re-sync. Identical to pi-repl's index.ts except that its Pi
3
3
  // imports come from ./pi-compat/index.js.
4
4
  import { DEFAULT_MAX_BYTES, DEFAULT_MAX_LINES, formatSize, truncateHead, truncateTail, } from "./pi-compat/index.js";
@@ -459,7 +459,9 @@ async function commandExists(pi, command, cwd) {
459
459
  }
460
460
  }
461
461
  async function execTmux(pi, args, cwd, timeout = 5_000) {
462
- const result = await pi.exec("tmux", args, { cwd, timeout });
462
+ // Unless the locale is UTF-8 (LANG is unset in some terminals), tmux prints
463
+ // the tabs in -F output as `_`; -u keeps them, so pane listings parse.
464
+ const result = await pi.exec("tmux", ["-u", ...args], { cwd, timeout });
463
465
  return {
464
466
  code: result.code ?? 1,
465
467
  stdout: result.stdout ?? "",
@@ -898,9 +900,8 @@ function buildPythonControlSource(runtime, code, doneFile, display) {
898
900
  " __pi_repl_ip = get_ipython()",
899
901
  " if __pi_repl_ip is None:",
900
902
  " raise RuntimeError('Expected IPython session, but get_ipython() returned None.')",
901
- ` __pi_repl_result = __pi_repl_ip.run_cell(${JSON.stringify(code)}, store_history=False)`,
902
- " if getattr(__pi_repl_result, 'error_in_exec', None) is None and getattr(__pi_repl_result, 'result', None) is not None:",
903
- " print(repr(__pi_repl_result.result))",
903
+ // run_cell shows the value itself (Out[N]: ...), so nothing more is printed here.
904
+ ` __pi_repl_ip.run_cell(${JSON.stringify(code)}, store_history=False)`,
904
905
  "except Exception:",
905
906
  " __pi_repl_traceback.print_exc()",
906
907
  "finally:",
@@ -12,7 +12,8 @@ const paneFormat = "#{pid}\t#{session_id}\t#{session_name}\t#{session_created}\t
12
12
  export function parseStopProcessTable(text) {
13
13
  return text.split("\n").filter((line) => line.trim()).map((line) => {
14
14
  const f = line.trim().split(/\s+/);
15
- if (f.length !== 11 || !f.slice(0, 4).every((v) => /^\d+$/.test(v) && Number.isSafeInteger(Number(v)))) throw new Error("Could not parse process ownership data; refusing unsafe shutdown.");
15
+ // macOS lists processes of `nobody` with uid -2; it never matches ours.
16
+ if (f.length !== 11 || !f.slice(0, 4).every((v, i) => (i === 3 ? /^-?\d+$/ : /^\d+$/).test(v) && Number.isSafeInteger(Number(v)))) throw new Error("Could not parse process ownership data; refusing unsafe shutdown.");
16
17
  return { pid: Number(f[0]), ppid: Number(f[1]), pgid: Number(f[2]), uid: Number(f[3]), tty: f[4], startedAt: f.slice(5, 10).join(" "), state: f[10] };
17
18
  });
18
19
  }
@@ -152,9 +153,16 @@ export class SessionStopProcesses {
152
153
  }
153
154
  }
154
155
 
155
- /** Explicit stop only. Never used by send, startup, reload, or shutdown hooks. */
156
- export async function stopVerifiedReplSession({ tmux, sessionName, snapshot = readStopProcessTable, signal = (pid, value) => process.kill(pid, value), currentPid = process.pid, uid = process.getuid?.(), graceMs = 500, termMs = 1000, killMs = 2000, findMarked = findOwnedGnuplotProcesses, snapshotSockets = snapshotGnuplotSockets, cleanupSockets = cleanupGnuplotSockets }) {
157
- if (typeof uid !== "number" || !/^pi-repl-(python|julia|r|ghci|clojure|ruby|java|octave|matlab|gnuplot|cpp)$/.test(sessionName)) throw new Error("Verified REPL shutdown requires a supported local Unix session.");
156
+ export const PI_REPL_STOP_SESSION_NAME = /^pi-repl-(python|julia|r|ghci|clojure|ruby|java|octave|matlab|gnuplot|cpp)$/;
157
+
158
+ /**
159
+ * Explicit stop only. Never used by send, startup, reload, or shutdown hooks.
160
+ * sessionNamePattern names the sessions this host owns; pi-studio passes its own.
161
+ */
162
+ export async function stopVerifiedReplSession({ tmux, sessionName, sessionNamePattern = PI_REPL_STOP_SESSION_NAME, snapshot = readStopProcessTable, signal = (pid, value) => process.kill(pid, value), currentPid = process.pid, uid = process.getuid?.(), graceMs = 500, termMs = 1000, killMs = 2000, findMarked = findOwnedGnuplotProcesses, snapshotSockets = snapshotGnuplotSockets, cleanupSockets = cleanupGnuplotSockets }) {
163
+ // A global or sticky pattern keeps state between tests; refuse it rather than misjudge a name.
164
+ if (!(sessionNamePattern instanceof RegExp) || sessionNamePattern.global || sessionNamePattern.sticky) throw new Error("Verified REPL shutdown needs a plain session-name pattern.");
165
+ if (typeof uid !== "number" || typeof sessionName !== "string" || !sessionNamePattern.test(sessionName)) throw new Error("Verified REPL shutdown requires a supported local Unix session.");
158
166
  const panes = await readPanes(tmux);
159
167
  const selected = panes.filter((p) => p.sessionName === sessionName);
160
168
  if (!selected.length) throw new Error("The selected session ended or changed before shutdown; no processes were signalled.");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agent-repl",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "pi-repl's shared tmux REPLs (Python, Julia, R and more) for Claude Code, Codex, OpenCode and other agents, over MCP and a CLI.",
5
5
  "keywords": [
6
6
  "repl",