pisesh 0.1.9 → 0.1.11
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 +11 -0
- package/README.md +6 -7
- package/bin/pisesh +54 -0
- package/extensions/sesh.ts +7 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.11] - 2026-07-14
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- `/sesh` now launches the CLI bundled with the pi package instead of requiring `pisesh` on the user's `PATH`.
|
|
9
|
+
- Clarified that the standalone `pisesh` shell command requires `npm install -g pisesh`.
|
|
10
|
+
|
|
11
|
+
## [0.1.10] — 2026-06-09
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- **Resume no longer crashes on sessions interrupted mid-tool-call.** If a session ended while a tool call was still in flight (e.g. an image generation cut off by a usage limit or a closed window), its transcript held a tool call with no recorded result. On resume, pi would re-fire that dead tool and the spawned process could exit non-zero — surfacing as `pisesh exited with code 1`, repeatedly. `resumeSession` now heals the target session first: every orphaned tool call gets a synthetic `[interrupted]` result injected before pi is spawned, so the resumed history is always well-formed and no dead tool is re-run. Idempotent, never throws, and writes a one-time `.bak-orphanheal` backup of the session before modifying it.
|
|
15
|
+
|
|
5
16
|
## [0.1.9] — 2026-06-03
|
|
6
17
|
|
|
7
18
|
### Fixed
|
package/README.md
CHANGED
|
@@ -65,23 +65,22 @@ pisesh is a **single-file Node script** (no dependencies, ~900 LoC) that gives y
|
|
|
65
65
|
|
|
66
66
|
## Getting started
|
|
67
67
|
|
|
68
|
-
###
|
|
68
|
+
### Install the `/sesh` command in pi (recommended)
|
|
69
69
|
|
|
70
70
|
```bash
|
|
71
|
-
# Install both the CLI and the /sesh slash command in one go
|
|
72
71
|
pi install npm:pisesh
|
|
73
72
|
```
|
|
74
73
|
|
|
75
|
-
This registers pisesh as a pi extension. Inside any pi session, type `/sesh`.
|
|
74
|
+
This registers pisesh as a pi extension. Inside any pi session, type `/sesh`. The extension runs its bundled CLI, so a global npm installation is not required.
|
|
76
75
|
|
|
77
|
-
###
|
|
76
|
+
### Install the standalone CLI
|
|
78
77
|
|
|
79
78
|
```bash
|
|
80
79
|
npm install -g pisesh
|
|
81
80
|
pisesh
|
|
82
81
|
```
|
|
83
82
|
|
|
84
|
-
|
|
83
|
+
The standalone `pisesh` shell command requires this global npm installation. It is separate from `pi install npm:pisesh`.
|
|
85
84
|
|
|
86
85
|
### From source (developers)
|
|
87
86
|
|
|
@@ -92,7 +91,7 @@ npm link # symlink ./bin/pisesh into your global PATH
|
|
|
92
91
|
pisesh --help
|
|
93
92
|
```
|
|
94
93
|
|
|
95
|
-
|
|
94
|
+
For local pi testing, run `pi install .` from the cloned repository so the extension and its bundled CLI stay together.
|
|
96
95
|
|
|
97
96
|
## Keys
|
|
98
97
|
|
|
@@ -183,7 +182,7 @@ Korean / Chinese / Japanese / fullwidth characters render **2 cells wide** in te
|
|
|
183
182
|
|
|
184
183
|
## Requirements
|
|
185
184
|
|
|
186
|
-
- **Node.js ≥ 18
|
|
185
|
+
- **Node.js ≥ 18 on `PATH`** (the `/sesh` extension uses `node` to run its bundled CLI)
|
|
187
186
|
- A terminal with ANSI escape and alternate screen buffer support, which covers basically every modern emulator:
|
|
188
187
|
- Windows: **Windows Terminal**, **WezTerm**, **Alacritty** ✅
|
|
189
188
|
- macOS: **iTerm2**, **Terminal.app**, **WezTerm**, **Alacritty**, **Kitty** ✅
|
package/bin/pisesh
CHANGED
|
@@ -838,8 +838,62 @@ function tryCopy(text) {
|
|
|
838
838
|
}
|
|
839
839
|
|
|
840
840
|
// ── Actions ───────────────────────────────────────────
|
|
841
|
+
// Heal a session before resuming. On resume, pi re-executes (or chokes on) any
|
|
842
|
+
// toolCall that has no recorded toolResult — e.g. an image generation that was
|
|
843
|
+
// interrupted when the prior session hit a usage limit or was closed mid-tool.
|
|
844
|
+
// Left unhealed, the spawned pi crashes (exit 1), which surfaces to the parent
|
|
845
|
+
// as "pisesh exited with code N". We inject a synthetic "[interrupted]"
|
|
846
|
+
// toolResult for every orphaned toolCall so the resumed history is always
|
|
847
|
+
// well-formed and pi never re-fires a dead tool. Idempotent; never throws.
|
|
848
|
+
function healOrphanedToolCalls(file) {
|
|
849
|
+
try {
|
|
850
|
+
const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
851
|
+
const resultIds = new Set();
|
|
852
|
+
for (const l of lines) {
|
|
853
|
+
let o; try { o = JSON.parse(l); } catch { continue; }
|
|
854
|
+
if (o && o.message && o.message.role === 'toolResult') resultIds.add(o.message.toolCallId);
|
|
855
|
+
}
|
|
856
|
+
const rnd = () => Math.random().toString(16).slice(2, 10);
|
|
857
|
+
const out = [];
|
|
858
|
+
let inserted = 0, prevSynthId = null;
|
|
859
|
+
for (const l of lines) {
|
|
860
|
+
let o; try { o = JSON.parse(l); } catch { out.push(l); continue; }
|
|
861
|
+
if (prevSynthId && o && typeof o.parentId !== 'undefined') { o.parentId = prevSynthId; prevSynthId = null; }
|
|
862
|
+
out.push(JSON.stringify(o));
|
|
863
|
+
const c = o && o.message && o.message.content;
|
|
864
|
+
if (o && o.message && o.message.role === 'assistant' && Array.isArray(c)) {
|
|
865
|
+
for (const b of c) {
|
|
866
|
+
if (b && b.type === 'toolCall' && b.id && !resultIds.has(b.id)) {
|
|
867
|
+
const synth = {
|
|
868
|
+
type: 'message', id: rnd(), parentId: o.id,
|
|
869
|
+
timestamp: new Date().toISOString(),
|
|
870
|
+
message: {
|
|
871
|
+
role: 'toolResult', toolCallId: b.id, toolName: b.name,
|
|
872
|
+
content: [{ type: 'text', text: '[tool call interrupted \u2014 the previous session ended before this tool finished (e.g. usage limit reached or session closed); no result was recorded]' }],
|
|
873
|
+
isError: true, timestamp: Date.now(),
|
|
874
|
+
},
|
|
875
|
+
};
|
|
876
|
+
out.push(JSON.stringify(synth));
|
|
877
|
+
resultIds.add(b.id);
|
|
878
|
+
prevSynthId = synth.id;
|
|
879
|
+
inserted++;
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (inserted > 0) {
|
|
885
|
+
try { const bak = file + '.bak-orphanheal'; if (!fs.existsSync(bak)) fs.copyFileSync(file, bak); } catch {}
|
|
886
|
+
fs.writeFileSync(file, out.join('\n') + '\n');
|
|
887
|
+
}
|
|
888
|
+
return inserted;
|
|
889
|
+
} catch { return 0; }
|
|
890
|
+
}
|
|
891
|
+
|
|
841
892
|
function resumeSession(s) {
|
|
842
893
|
if (!s) return;
|
|
894
|
+
// Heal orphaned tool calls before handing off, so resume can't crash the
|
|
895
|
+
// spawned pi (see healOrphanedToolCalls).
|
|
896
|
+
try { healOrphanedToolCalls(s.file); } catch {}
|
|
843
897
|
// Leave alt screen + show cursor so the spawned pi takes over a clean
|
|
844
898
|
// main-buffer terminal (it will manage its own alt screen).
|
|
845
899
|
process.stdout.write(A.exitAlt + A.showC);
|
package/extensions/sesh.ts
CHANGED
|
@@ -10,13 +10,17 @@
|
|
|
10
10
|
* or just pressed `q` — control returns to the original pi session and the
|
|
11
11
|
* TUI is restored.
|
|
12
12
|
*
|
|
13
|
-
*
|
|
13
|
+
* Bundled CLI tool: ../bin/pisesh (single-file Node TUI, no deps)
|
|
14
14
|
* Favorites file: ~/.pi/agent/favorites.json
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { spawn } from "node:child_process";
|
|
18
|
+
import path from "node:path";
|
|
18
19
|
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
19
20
|
|
|
21
|
+
// Static package path, no user-controlled segments.
|
|
22
|
+
const PISESH_CLI = path.resolve(__dirname, "../bin/pisesh"); // pi-lens-ignore: ts-path-traversal
|
|
23
|
+
|
|
20
24
|
function runPisesh(
|
|
21
25
|
currentSessionId: string | undefined,
|
|
22
26
|
): Promise<number | null> {
|
|
@@ -25,7 +29,7 @@ function runPisesh(
|
|
|
25
29
|
// already detached so this is safe.
|
|
26
30
|
// PISESH_CURRENT_SESSION lets pisesh flag the row that belongs to the
|
|
27
31
|
// pi instance that just spawned it (rendered with a [NOW] badge).
|
|
28
|
-
const child = spawn("
|
|
32
|
+
const child = spawn("node", [PISESH_CLI], {
|
|
29
33
|
stdio: "inherit",
|
|
30
34
|
env: {
|
|
31
35
|
...process.env,
|
|
@@ -85,7 +89,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
85
89
|
if (code === 0 || code === null) {
|
|
86
90
|
ctx.ui.notify("Returned from pisesh", "info");
|
|
87
91
|
} else if (code === 127) {
|
|
88
|
-
ctx.ui.notify("pisesh
|
|
92
|
+
ctx.ui.notify("pisesh failed to launch", "error");
|
|
89
93
|
} else {
|
|
90
94
|
ctx.ui.notify(`pisesh exited with code ${code}`, "warning");
|
|
91
95
|
}
|