@grrrck/pi-clear 0.1.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.
- package/CHANGELOG.md +14 -0
- package/LICENSE +21 -0
- package/README.md +73 -0
- package/index.ts +129 -0
- package/package.json +32 -0
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [0.1.0] - 2026-10-02
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- `/clear` command that starts a fresh session while preserving the current model and thinking level.
|
|
13
|
+
- Waits for the agent to become idle before clearing.
|
|
14
|
+
- Warnings when clearing is cancelled, the model is no longer available, or its auth is missing.
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Garrick Aden-Buie
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
# pi-clear
|
|
2
|
+
|
|
3
|
+
A [pi](https://github.com/earendil-works/pi) extension that adds a `/clear` command: start a fresh session **without losing your model**.
|
|
4
|
+
|
|
5
|
+
## Why?
|
|
6
|
+
|
|
7
|
+
Pi's built-in `/new` command starts a new session, but the new session resolves its model from pi's defaults chain — CLI arguments, scoped models, the saved default in settings, then the first available model with auth.
|
|
8
|
+
|
|
9
|
+
If you switched models mid-session — with `/model`, model cycling, or an extension — that choice is recorded as a `model_change` entry in the *current* session transcript, so `/new` never sees it. Your carefully chosen model quietly reverts to the default.
|
|
10
|
+
|
|
11
|
+
`/clear` starts an equally fresh session, then restores the model and thinking level you were using.
|
|
12
|
+
|
|
13
|
+
## Install
|
|
14
|
+
|
|
15
|
+
```sh
|
|
16
|
+
pi install npm:@gadenbuie/pi-clear
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or try it without installing:
|
|
20
|
+
|
|
21
|
+
```sh
|
|
22
|
+
pi -e npm:@gadenbuie/pi-clear
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
```text
|
|
28
|
+
/clear
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Behavior
|
|
32
|
+
|
|
33
|
+
When `/clear` runs, the extension:
|
|
34
|
+
|
|
35
|
+
1. waits for the agent to become idle if needed
|
|
36
|
+
2. captures the active model and thinking level
|
|
37
|
+
3. starts a new session via `ctx.newSession()`
|
|
38
|
+
4. restores the captured model and thinking level in the new session
|
|
39
|
+
5. shows a warning if clearing is cancelled or the captured model can no longer be applied (e.g. its auth was removed)
|
|
40
|
+
|
|
41
|
+
Because the model is applied with `pi.setModel()`, it's recorded in the new session's transcript — so it also survives `/resume`.
|
|
42
|
+
|
|
43
|
+
## Notes
|
|
44
|
+
|
|
45
|
+
- `/clear` only registers the slash command; it does not bind a keyboard shortcut, so it won't conflict with other extensions (such as `pi-mono-clear`, which uses `Ctrl+Shift+L`).
|
|
46
|
+
- The model handoff is in-process only: it never touches the filesystem, is invisible to other pi processes, and is discarded if pi exits between clearing and restoring.
|
|
47
|
+
- Like `/new`, `/clear` does not carry over conversation history, compaction summaries, or session name.
|
|
48
|
+
|
|
49
|
+
## How it works
|
|
50
|
+
|
|
51
|
+
pi tears down the current runtime when a session is replaced, and extension instances are recreated for the new session. Session-bound objects can't be reused across that boundary, and the post-switch context has no model setter — but the replacement happens within the same process. So `/clear` captures the model as plain data in a `globalThis` property, and the new session's extension instance consumes it on `session_start` (reason `new`) and applies it with `pi.setModel()`. The handoff is consumed exactly once, so it can never leak into a later session.
|
|
52
|
+
|
|
53
|
+
## Development
|
|
54
|
+
|
|
55
|
+
Load the extension directly for local testing:
|
|
56
|
+
|
|
57
|
+
```sh
|
|
58
|
+
pi -e ./index.ts
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
There's also an RPC smoke test that verifies the core behavior end-to-end: it starts pi in RPC mode, switches to a different model and thinking level, runs `/clear`, and asserts both survive the new session:
|
|
62
|
+
|
|
63
|
+
```sh
|
|
64
|
+
python3 scripts/rpc-smoke-test.py
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Related extensions
|
|
68
|
+
|
|
69
|
+
- [`pi-mono-clear`](https://github.com/emanuelcasco/pi-mono-extensions) — a `/clear` alias for `/new` (does not preserve the model)
|
|
70
|
+
|
|
71
|
+
## License
|
|
72
|
+
|
|
73
|
+
[MIT](LICENSE)
|
package/index.ts
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi-clear — `/clear` for pi: start a fresh session without losing your model.
|
|
3
|
+
*
|
|
4
|
+
* The built-in `/new` command starts a new session, but the new runtime
|
|
5
|
+
* resolves its model from the defaults chain (CLI args → scoped models →
|
|
6
|
+
* saved default in settings → first available model with auth). A model you
|
|
7
|
+
* switched to mid-session — via `/model`, `pi.setModel()`, or model cycling —
|
|
8
|
+
* is recorded as a `model_change` entry in the *current* session transcript,
|
|
9
|
+
* so it never reaches the new session.
|
|
10
|
+
*
|
|
11
|
+
* `/clear` starts an equally fresh session, then restores the model and
|
|
12
|
+
* thinking level that were active when you cleared.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* /clear
|
|
16
|
+
*
|
|
17
|
+
* Mechanism:
|
|
18
|
+
* 1. The `/clear` handler captures the active model (provider + id) and
|
|
19
|
+
* thinking level as plain data into a `globalThis` property. Session
|
|
20
|
+
* replacement (`ctx.newSession()`) tears down the current runtime and
|
|
21
|
+
* recreates extension instances, but stays within the same process, so
|
|
22
|
+
* a `globalThis` property safely crosses that boundary while remaining
|
|
23
|
+
* invisible to other pi processes and dying with crashes.
|
|
24
|
+
* 2. The new instance's `session_start` handler (reason: "new") consumes
|
|
25
|
+
* the handoff and applies the captured model and thinking level via
|
|
26
|
+
* `pi.setModel()` / `pi.setThinkingLevel()`, which record them in the
|
|
27
|
+
* new session so they also survive `/resume`.
|
|
28
|
+
*/
|
|
29
|
+
|
|
30
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
31
|
+
|
|
32
|
+
interface ClearHandoff {
|
|
33
|
+
provider: string;
|
|
34
|
+
modelId: string;
|
|
35
|
+
thinkingLevel?: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const HANDOFF_KEY = "__pi_clear_handoff__";
|
|
39
|
+
|
|
40
|
+
const THINKING_LEVELS = [
|
|
41
|
+
"off",
|
|
42
|
+
"minimal",
|
|
43
|
+
"low",
|
|
44
|
+
"medium",
|
|
45
|
+
"high",
|
|
46
|
+
"xhigh",
|
|
47
|
+
"max",
|
|
48
|
+
] as const;
|
|
49
|
+
type ThinkingLevel = (typeof THINKING_LEVELS)[number];
|
|
50
|
+
|
|
51
|
+
function isThinkingLevel(value: string): value is ThinkingLevel {
|
|
52
|
+
return (THINKING_LEVELS as readonly string[]).includes(value);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function writeHandoff(handoff: ClearHandoff): void {
|
|
56
|
+
(globalThis as Record<string, unknown>)[HANDOFF_KEY] = handoff;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/** Consume the handoff: read it and remove it so it can only apply once. */
|
|
60
|
+
function takeHandoff(): ClearHandoff | undefined {
|
|
61
|
+
const store = globalThis as Record<string, unknown>;
|
|
62
|
+
const handoff = store[HANDOFF_KEY];
|
|
63
|
+
delete store[HANDOFF_KEY];
|
|
64
|
+
return handoff as ClearHandoff | undefined;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export default function (pi: ExtensionAPI) {
|
|
68
|
+
pi.registerCommand("clear", {
|
|
69
|
+
description: "Start a fresh session, keeping the current model and thinking level",
|
|
70
|
+
handler: async (_args, ctx) => {
|
|
71
|
+
try {
|
|
72
|
+
if (!ctx.isIdle()) {
|
|
73
|
+
await ctx.waitForIdle();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const { model } = ctx;
|
|
77
|
+
if (model) {
|
|
78
|
+
// Capture plain data only: it must survive session replacement.
|
|
79
|
+
writeHandoff({
|
|
80
|
+
provider: model.provider,
|
|
81
|
+
modelId: model.id,
|
|
82
|
+
thinkingLevel: ctx.thinkingLevel,
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
const result = await ctx.newSession();
|
|
87
|
+
// Normally session_start already consumed the handoff; clear any
|
|
88
|
+
// leftover so it can never apply to a later, unrelated /new.
|
|
89
|
+
takeHandoff();
|
|
90
|
+
if (result.cancelled) {
|
|
91
|
+
ctx.ui.notify("Clear cancelled by extension", "warning");
|
|
92
|
+
}
|
|
93
|
+
} catch (err) {
|
|
94
|
+
takeHandoff();
|
|
95
|
+
ctx.ui.notify(`Clear failed: ${err instanceof Error ? err.message : String(err)}`, "error");
|
|
96
|
+
}
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
pi.on("session_start", async (event, ctx) => {
|
|
101
|
+
if (event.reason !== "new") return;
|
|
102
|
+
|
|
103
|
+
const handoff = takeHandoff();
|
|
104
|
+
if (!handoff) return;
|
|
105
|
+
|
|
106
|
+
try {
|
|
107
|
+
const model = ctx.modelRegistry.find(handoff.provider, handoff.modelId);
|
|
108
|
+
if (!model) {
|
|
109
|
+
ctx.ui.notify(`pi-clear: model ${handoff.provider}/${handoff.modelId} is no longer available`, "warning");
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const applied = await pi.setModel(model);
|
|
114
|
+
if (!applied) {
|
|
115
|
+
ctx.ui.notify(`pi-clear: no auth configured for ${handoff.provider}/${handoff.modelId}`, "warning");
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (handoff.thinkingLevel && isThinkingLevel(handoff.thinkingLevel)) {
|
|
120
|
+
pi.setThinkingLevel(handoff.thinkingLevel);
|
|
121
|
+
}
|
|
122
|
+
} catch (err) {
|
|
123
|
+
ctx.ui.notify(
|
|
124
|
+
`pi-clear: could not restore model ${handoff.provider}/${handoff.modelId}: ${err instanceof Error ? err.message : String(err)}`,
|
|
125
|
+
"warning",
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@grrrck/pi-clear",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Pi extension that starts a fresh session with /clear while keeping your current model and thinking level",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"pi-extension",
|
|
8
|
+
"clear",
|
|
9
|
+
"session",
|
|
10
|
+
"model"
|
|
11
|
+
],
|
|
12
|
+
"license": "MIT",
|
|
13
|
+
"author": "Garrick Aden-Buie (https://garrickadenbuie.com)",
|
|
14
|
+
"type": "module",
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"@earendil-works/pi-coding-agent": "*"
|
|
17
|
+
},
|
|
18
|
+
"pi": {
|
|
19
|
+
"extensions": [
|
|
20
|
+
"./index.ts"
|
|
21
|
+
]
|
|
22
|
+
},
|
|
23
|
+
"repository": {
|
|
24
|
+
"type": "git",
|
|
25
|
+
"url": "git+https://github.com/gadenbuie/pi-clear.git"
|
|
26
|
+
},
|
|
27
|
+
"bugs": {
|
|
28
|
+
"url": "https://github.com/gadenbuie/pi-clear/issues"
|
|
29
|
+
},
|
|
30
|
+
"homepage": "https://github.com/gadenbuie/pi-clear#readme",
|
|
31
|
+
"files": ["index.ts", "README.md", "LICENSE", "CHANGELOG.md"]
|
|
32
|
+
}
|