pisesh 0.1.10 → 0.1.12
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 +12 -0
- package/README.md +6 -7
- package/bin/pisesh +43 -12
- package/extensions/sesh.ts +7 -3
- package/package.json +2 -2
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,18 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [0.1.12] - 2026-07-20
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
- Skip interrupted tool calls from assistant turns that ended with `error` or `aborted`, preventing OpenAI Responses from receiving an unmatched `function_call_output` after resume or fork.
|
|
9
|
+
- Remove incompatible synthetic results previously written by pisesh and repair their child links when affected sessions are resumed.
|
|
10
|
+
|
|
11
|
+
## [0.1.11] - 2026-07-14
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
- `/sesh` now launches the CLI bundled with the pi package instead of requiring `pisesh` on the user's `PATH`.
|
|
15
|
+
- Clarified that the standalone `pisesh` shell command requires `npm install -g pisesh`.
|
|
16
|
+
|
|
5
17
|
## [0.1.10] — 2026-06-09
|
|
6
18
|
|
|
7
19
|
### 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
|
@@ -843,25 +843,54 @@ function tryCopy(text) {
|
|
|
843
843
|
// interrupted when the prior session hit a usage limit or was closed mid-tool.
|
|
844
844
|
// Left unhealed, the spawned pi crashes (exit 1), which surfaces to the parent
|
|
845
845
|
// as "pisesh exited with code N". We inject a synthetic "[interrupted]"
|
|
846
|
-
// toolResult for
|
|
847
|
-
//
|
|
846
|
+
// toolResult for unfinished valid turns, but not errored/aborted assistant turns
|
|
847
|
+
// that pi omits during replay. Old incompatible synthetic results are removed.
|
|
848
|
+
// Idempotent; never throws.
|
|
848
849
|
function healOrphanedToolCalls(file) {
|
|
849
850
|
try {
|
|
850
851
|
const lines = fs.readFileSync(file, 'utf8').split('\n').filter(Boolean);
|
|
852
|
+
const entries = lines.map(raw => {
|
|
853
|
+
try { return { raw, value: JSON.parse(raw) }; } catch { return { raw, value: null }; }
|
|
854
|
+
});
|
|
855
|
+
const droppedToolCallIds = new Set();
|
|
856
|
+
for (const { value: o } of entries) {
|
|
857
|
+
const m = o && o.message;
|
|
858
|
+
if (m && m.role === 'assistant' && (m.stopReason === 'error' || m.stopReason === 'aborted')) {
|
|
859
|
+
for (const b of Array.isArray(m.content) ? m.content : []) {
|
|
860
|
+
if (b && b.type === 'toolCall' && b.id) droppedToolCallIds.add(b.id);
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
const removedParents = new Map();
|
|
866
|
+
const kept = entries.filter(({ value: o }) => {
|
|
867
|
+
const m = o && o.message;
|
|
868
|
+
const text = m && Array.isArray(m.content) && m.content.find(b => b && b.type === 'text');
|
|
869
|
+
const isBadSynthetic = m && m.role === 'toolResult'
|
|
870
|
+
&& droppedToolCallIds.has(m.toolCallId)
|
|
871
|
+
&& text && text.text.startsWith('[tool call interrupted');
|
|
872
|
+
if (isBadSynthetic) removedParents.set(o.id, o.parentId);
|
|
873
|
+
return !isBadSynthetic;
|
|
874
|
+
});
|
|
875
|
+
for (const { value: o } of kept) {
|
|
876
|
+
if (!o || typeof o.parentId === 'undefined') continue;
|
|
877
|
+
while (removedParents.has(o.parentId)) o.parentId = removedParents.get(o.parentId);
|
|
878
|
+
}
|
|
879
|
+
|
|
851
880
|
const resultIds = new Set();
|
|
852
|
-
for (const
|
|
853
|
-
let o; try { o = JSON.parse(l); } catch { continue; }
|
|
881
|
+
for (const { value: o } of kept) {
|
|
854
882
|
if (o && o.message && o.message.role === 'toolResult') resultIds.add(o.message.toolCallId);
|
|
855
883
|
}
|
|
856
884
|
const rnd = () => Math.random().toString(16).slice(2, 10);
|
|
857
885
|
const out = [];
|
|
858
886
|
let inserted = 0, prevSynthId = null;
|
|
859
|
-
for (const
|
|
860
|
-
|
|
861
|
-
if (prevSynthId &&
|
|
887
|
+
for (const { raw, value: o } of kept) {
|
|
888
|
+
if (!o) { out.push(raw); continue; }
|
|
889
|
+
if (prevSynthId && typeof o.parentId !== 'undefined') { o.parentId = prevSynthId; prevSynthId = null; }
|
|
862
890
|
out.push(JSON.stringify(o));
|
|
863
|
-
const
|
|
864
|
-
|
|
891
|
+
const m = o.message;
|
|
892
|
+
const c = m && m.content;
|
|
893
|
+
if (m && m.role === 'assistant' && m.stopReason !== 'error' && m.stopReason !== 'aborted' && Array.isArray(c)) {
|
|
865
894
|
for (const b of c) {
|
|
866
895
|
if (b && b.type === 'toolCall' && b.id && !resultIds.has(b.id)) {
|
|
867
896
|
const synth = {
|
|
@@ -881,11 +910,12 @@ function healOrphanedToolCalls(file) {
|
|
|
881
910
|
}
|
|
882
911
|
}
|
|
883
912
|
}
|
|
884
|
-
|
|
913
|
+
const changed = removedParents.size + inserted;
|
|
914
|
+
if (changed > 0) {
|
|
885
915
|
try { const bak = file + '.bak-orphanheal'; if (!fs.existsSync(bak)) fs.copyFileSync(file, bak); } catch {}
|
|
886
916
|
fs.writeFileSync(file, out.join('\n') + '\n');
|
|
887
917
|
}
|
|
888
|
-
return
|
|
918
|
+
return changed;
|
|
889
919
|
} catch { return 0; }
|
|
890
920
|
}
|
|
891
921
|
|
|
@@ -1010,4 +1040,5 @@ function main() {
|
|
|
1010
1040
|
render();
|
|
1011
1041
|
}
|
|
1012
1042
|
|
|
1013
|
-
main();
|
|
1043
|
+
if (require.main === module) main();
|
|
1044
|
+
else module.exports = { healOrphanedToolCalls };
|
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
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pisesh",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.12",
|
|
4
4
|
"description": "Bookmark, search, and resume pi coding-agent sessions with a fast keyboard-driven TUI.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -46,7 +46,7 @@
|
|
|
46
46
|
"image": "https://raw.githubusercontent.com/Blue-B/pisesh/main/assets/preview.png"
|
|
47
47
|
},
|
|
48
48
|
"scripts": {
|
|
49
|
-
"test": "node --check bin/pisesh &&
|
|
49
|
+
"test": "node --check bin/pisesh && node --test test/*.test.js",
|
|
50
50
|
"prepublishOnly": "npm test"
|
|
51
51
|
}
|
|
52
52
|
}
|