@fyeeme/pi-session-name 1.0.1 → 1.0.3
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 +13 -9
- package/README.md +125 -46
- package/index.ts +8 -6
- package/package.json +41 -13
package/CHANGELOG.md
CHANGED
|
@@ -7,26 +7,30 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
-
## [1.0.
|
|
10
|
+
## [1.0.3] - 2026-09-09
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Project config path uses `CONFIG_DIR_NAME` instead of a hardcoded `.pi`.
|
|
15
|
+
- Title generation forwards `ctx.signal` so aborts cancel the nested model call.
|
|
16
|
+
|
|
17
|
+
## [1.0.2] - 2025-07-25
|
|
11
18
|
|
|
12
19
|
### Fixed
|
|
13
20
|
|
|
14
21
|
- Race condition where auto-generated title could overwrite manual rename when `generateTitle` async call completes after user renamed the session
|
|
15
22
|
|
|
23
|
+
## [1.0.1] - 2025-07-25
|
|
24
|
+
|
|
16
25
|
### Added
|
|
17
26
|
|
|
18
|
-
- `/rename [name]` command: manually rename the current session on demand. With
|
|
19
|
-
a name argument it sets that name; with no argument it auto-generates one from
|
|
20
|
-
the conversation. Invoking it locks out background auto-naming (manual
|
|
21
|
-
control), consistent with `/name`.
|
|
27
|
+
- `/rename [name]` command: manually rename the current session on demand. With a name argument it sets that name; with no argument it auto-generates one from the conversation. Invoking it locks out background auto-naming (manual control), consistent with `/name`.
|
|
22
28
|
|
|
23
29
|
### Changed
|
|
24
30
|
|
|
25
|
-
- Title-generation prompt now favors descriptive titles (key entity + action +
|
|
26
|
-
goal, ~15-40 characters) over terse labels. Previously the prompt emphasized
|
|
27
|
-
"short/concise", which produced overly brief session names.
|
|
31
|
+
- Title-generation prompt now favors descriptive titles (key entity + action + goal, ~15-40 characters) over terse labels. Previously the prompt emphasized "short/concise", which produced overly brief session names.
|
|
28
32
|
|
|
29
|
-
## [1.0.0] -
|
|
33
|
+
## [1.0.0] - 2025-07-20
|
|
30
34
|
|
|
31
35
|
### Added
|
|
32
36
|
|
package/README.md
CHANGED
|
@@ -1,72 +1,151 @@
|
|
|
1
1
|
# pi-session-name
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
[](https://www.npmjs.com/package/@fyeeme/pi-session-name)
|
|
4
|
+
[](LICENSE)
|
|
5
5
|
|
|
6
|
-
|
|
6
|
+
Auto-name [pi](https://pi.dev) sessions with a short LLM-generated title so `--resume` lists are easy to scan — instead of the raw first message.
|
|
7
|
+
|
|
8
|
+
## Features
|
|
9
|
+
|
|
10
|
+
- **First-mode** (default) — names the session once on first agent response, then leaves it alone
|
|
11
|
+
- **Auto-mode** — re-evaluates each turn; the title tracks the current topic
|
|
12
|
+
- **Never overwrites manual names** — detects `/name`, `--name`, the resume picker's rename, or any other extension calling `setSessionName`, and locks itself for the rest of the session
|
|
13
|
+
- **`/rename [name]`** — rename the current session on demand. With an argument it sets that name; without, it generates one from the conversation
|
|
14
|
+
- **Language-aware** — titles use the same language as your first message
|
|
15
|
+
- **Descriptive titles** — key entity + action + goal (~15-40 chars), not terse labels
|
|
16
|
+
- **Graceful failure** — model unavailable or no API key? Stays silent, never blocks the session
|
|
17
|
+
|
|
18
|
+
## Prerequisites
|
|
19
|
+
|
|
20
|
+
- [pi](https://pi.dev) >= 0.80.0 (uses `agent_settled` / `session_info_changed` events)
|
|
21
|
+
|
|
22
|
+
## Installation
|
|
23
|
+
|
|
24
|
+
### As a pi package (recommended)
|
|
7
25
|
|
|
8
26
|
```bash
|
|
9
27
|
# Global (user) install
|
|
10
|
-
pi install
|
|
28
|
+
pi install @fyeeme/pi-session-name
|
|
11
29
|
|
|
12
30
|
# Project-local (.pi/settings.json)
|
|
13
|
-
pi install -l
|
|
31
|
+
pi install -l @fyeeme/pi-session-name
|
|
14
32
|
|
|
15
33
|
# Try once without saving
|
|
16
|
-
pi -e
|
|
34
|
+
pi -e @fyeeme/pi-session-name
|
|
17
35
|
```
|
|
18
36
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
37
|
+
### Manual (development)
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
# Clone the repo
|
|
41
|
+
git clone https://github.com/fyeeme/pi-packages.git
|
|
42
|
+
cd pi-packages/packages/extensions/pi-session-name
|
|
43
|
+
|
|
44
|
+
# Symlink to pi extensions
|
|
45
|
+
ln -s "$(pwd)" ~/.pi/agent/extensions/pi-session-name
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Usage
|
|
49
|
+
|
|
50
|
+
No configuration needed for the default `first` mode. Just install and use pi — your first session will auto-name itself.
|
|
51
|
+
|
|
52
|
+
If you want to change behavior, see [Configuration](#configuration).
|
|
53
|
+
|
|
54
|
+
### Commands
|
|
55
|
+
|
|
56
|
+
| Command | Description |
|
|
57
|
+
|---------|-------------|
|
|
58
|
+
| `/rename <name>` | Rename session to `<name>` and lock out auto-naming |
|
|
59
|
+
| `/rename` | Generate a descriptive name from the current conversation |
|
|
60
|
+
|
|
61
|
+
> **Note**: `/rename` is a manual action — once used, auto-naming is locked for the rest of the session. This is consistent with pi's built-in `/name` command.
|
|
39
62
|
|
|
40
63
|
## Configuration
|
|
41
64
|
|
|
42
65
|
Create `.pi/session-name.json` in your project root:
|
|
43
66
|
|
|
44
67
|
```json
|
|
45
|
-
{
|
|
68
|
+
{
|
|
69
|
+
"mode": "auto",
|
|
70
|
+
"maxLength": 200,
|
|
71
|
+
"model": { "provider": "openai", "id": "gpt-4o-mini" }
|
|
72
|
+
}
|
|
46
73
|
```
|
|
47
74
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
|
51
|
-
|
|
52
|
-
| `
|
|
75
|
+
### Options
|
|
76
|
+
|
|
77
|
+
| Option | Default | Env override | Description |
|
|
78
|
+
|--------|---------|--------------|-------------|
|
|
79
|
+
| `mode` | `"first"` | `PI_SESSION_NAME_MODE` | `"first"` — name once; `"auto"` — re-evaluate each turn |
|
|
80
|
+
| `maxLength` | `200` | `PI_SESSION_NAME_MAX_LENGTH` | Character cap for generated titles |
|
|
81
|
+
| `enabled` | `true` | `PI_SESSION_NAME_ENABLED=false` | Master switch to disable auto-naming |
|
|
53
82
|
| `model` | current session model | `PI_SESSION_NAME_MODEL_PROVIDER` + `PI_SESSION_NAME_MODEL_ID` | Override the model used to generate titles |
|
|
54
83
|
|
|
55
|
-
By default the extension uses the model you're already chatting with (`ctx.model`),
|
|
56
|
-
|
|
84
|
+
By default the extension uses the model you're already chatting with (`ctx.model`), so no extra API key is needed.
|
|
85
|
+
|
|
86
|
+
### Environment variables only
|
|
87
|
+
|
|
88
|
+
If you prefer environment variables over a config file:
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
export PI_SESSION_NAME_MODE=auto
|
|
92
|
+
export PI_SESSION_NAME_MAX_LENGTH=150
|
|
93
|
+
export PI_SESSION_NAME_ENABLED=true
|
|
94
|
+
export PI_SESSION_NAME_MODEL_PROVIDER=openai
|
|
95
|
+
export PI_SESSION_NAME_MODEL_ID=gpt-4o-mini
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## How it works
|
|
99
|
+
|
|
100
|
+
1. On `agent_settled` (after the first turn completes), the extension builds a condensed text of the conversation
|
|
101
|
+
2. It asks the LLM (same model as the session, unless overridden) to generate a descriptive title
|
|
102
|
+
3. The title is set via `pi.setSessionName()`, which updates the resume picker immediately
|
|
103
|
+
4. In `first` mode, it stops there. In `auto` mode, each subsequent turn re-evaluates: the LLM returns either `KEEP` or a new title
|
|
104
|
+
5. If a manual rename is detected (`session_info_changed` with a name the extension didn't set), auto-naming locks permanently
|
|
105
|
+
|
|
106
|
+
## Smoke test
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# 1. Fresh session with auto-naming
|
|
110
|
+
pi -e @fyeeme/pi-session-name
|
|
111
|
+
# Ask a question, wait for reply → resume picker shows a generated title
|
|
112
|
+
|
|
113
|
+
# 2. Manual rename protection
|
|
114
|
+
/name foo
|
|
115
|
+
# Chat a few turns → name stays "foo" (locked)
|
|
116
|
+
|
|
117
|
+
# 3. Auto mode: topic tracking
|
|
118
|
+
# Set mode to "auto", shift topic across turns → name updates; same topic → stays
|
|
119
|
+
|
|
120
|
+
# 4. /rename command
|
|
121
|
+
/rename foo # → name is "foo", locked
|
|
122
|
+
/rename # → generates a fresh name from the conversation
|
|
123
|
+
|
|
124
|
+
# 5. Graceful degradation (unset model's API key)
|
|
125
|
+
# → No errors, session runs normally
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## API (for extension developers)
|
|
129
|
+
|
|
130
|
+
This extension exposes utilities that other extensions can import:
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import {
|
|
134
|
+
buildConversationText,
|
|
135
|
+
cleanTitle,
|
|
136
|
+
buildFirstPrompt,
|
|
137
|
+
buildAutoPrompt,
|
|
138
|
+
loadConfig,
|
|
139
|
+
generateTitle,
|
|
140
|
+
} from "@fyeeme/pi-session-name";
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
See `index.ts` for complete type signatures.
|
|
57
144
|
|
|
58
|
-
##
|
|
145
|
+
## Changelog
|
|
59
146
|
|
|
60
|
-
|
|
61
|
-
stays silent — your session is never blocked. It retries on the next turn while
|
|
62
|
-
the session is still unnamed.
|
|
147
|
+
See [CHANGELOG.md](CHANGELOG.md).
|
|
63
148
|
|
|
64
|
-
##
|
|
149
|
+
## License
|
|
65
150
|
|
|
66
|
-
|
|
67
|
-
fresh session, wait for the reply → the resume picker shows a generated title.
|
|
68
|
-
2. `/name foo`, chat a few turns → name stays `foo` (locked).
|
|
69
|
-
3. `mode: "auto"`: shift topic across turns → name updates; same topic → stays.
|
|
70
|
-
4. `/rename foo` → name becomes `foo` and stays (locked). `/rename` with no
|
|
71
|
-
argument → generates a fresh name from the conversation.
|
|
72
|
-
5. Unset the model's API key → no errors, session runs normally.
|
|
151
|
+
MIT
|
package/index.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { join } from "node:path";
|
|
3
|
-
import type
|
|
3
|
+
import { CONFIG_DIR_NAME, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent";
|
|
4
4
|
import type { Model } from "@earendil-works/pi-ai";
|
|
5
5
|
import { complete } from "@earendil-works/pi-ai/compat";
|
|
6
6
|
|
|
@@ -122,7 +122,7 @@ const DEFAULT_CONFIG: SessionNameConfig = { mode: "first", enabled: true, maxLen
|
|
|
122
122
|
|
|
123
123
|
export function loadConfig(cwd: string, env: Record<string, string | undefined> = process.env): SessionNameConfig {
|
|
124
124
|
let fileCfg: Partial<SessionNameConfig> = {};
|
|
125
|
-
const file = join(cwd,
|
|
125
|
+
const file = join(cwd, CONFIG_DIR_NAME, "session-name.json");
|
|
126
126
|
try {
|
|
127
127
|
fileCfg = JSON.parse(readFileSync(file, "utf8")) as Partial<SessionNameConfig>;
|
|
128
128
|
} catch {
|
|
@@ -168,6 +168,7 @@ export async function generateTitle(
|
|
|
168
168
|
prompt: string,
|
|
169
169
|
auth: ModelAuth,
|
|
170
170
|
completeFn: typeof complete = complete,
|
|
171
|
+
signal?: AbortSignal,
|
|
171
172
|
): Promise<string> {
|
|
172
173
|
const response = await completeFn(
|
|
173
174
|
auth.model,
|
|
@@ -176,7 +177,8 @@ export async function generateTitle(
|
|
|
176
177
|
{ role: "user", content: [{ type: "text", text: prompt }], timestamp: Date.now() },
|
|
177
178
|
],
|
|
178
179
|
},
|
|
179
|
-
|
|
180
|
+
// ctx.signal (undefined while idle) lets Esc/abort cancel the nested call.
|
|
181
|
+
{ apiKey: auth.apiKey, headers: auth.headers, signal },
|
|
180
182
|
);
|
|
181
183
|
return response.content
|
|
182
184
|
.filter((c): c is { type: "text"; text: string } => c.type === "text")
|
|
@@ -224,9 +226,9 @@ export default function (pi: ExtensionAPI): void {
|
|
|
224
226
|
|
|
225
227
|
let title: string | null;
|
|
226
228
|
if (cfg.mode === "first" || !currentName) {
|
|
227
|
-
title = cleanTitle(await generateTitle(buildFirstPrompt(text, cfg), auth), cfg.maxLength);
|
|
229
|
+
title = cleanTitle(await generateTitle(buildFirstPrompt(text, cfg), auth, complete, ctx.signal), cfg.maxLength);
|
|
228
230
|
} else {
|
|
229
|
-
const verdict = await generateTitle(buildAutoPrompt(currentName, text, cfg), auth);
|
|
231
|
+
const verdict = await generateTitle(buildAutoPrompt(currentName, text, cfg), auth, complete, ctx.signal);
|
|
230
232
|
title = /^keep$/i.test(verdict.trim()) ? null : cleanTitle(verdict, cfg.maxLength);
|
|
231
233
|
}
|
|
232
234
|
if (!title) return;
|
|
@@ -276,7 +278,7 @@ export default function (pi: ExtensionAPI): void {
|
|
|
276
278
|
return;
|
|
277
279
|
}
|
|
278
280
|
try {
|
|
279
|
-
const title = cleanTitle(await generateTitle(buildFirstPrompt(text, cfg), auth), cfg.maxLength);
|
|
281
|
+
const title = cleanTitle(await generateTitle(buildFirstPrompt(text, cfg), auth, complete, ctx.signal), cfg.maxLength);
|
|
280
282
|
if (!title) {
|
|
281
283
|
ctx.ui.notify("Could not generate a name from the model response", "warning");
|
|
282
284
|
return;
|
package/package.json
CHANGED
|
@@ -1,26 +1,54 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fyeeme/pi-session-name",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.3",
|
|
4
4
|
"description": "Auto-name pi sessions with a short LLM-generated title so --resume lists are easy to scan. Supports first (once) and auto (content-aware rename) modes; never overwrites manual names.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
7
7
|
"author": "fyeeme",
|
|
8
|
-
"publishConfig": {
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"repository": {
|
|
12
|
+
"type": "git",
|
|
13
|
+
"url": "git+https://github.com/fyeeme/pi-packages.git"
|
|
14
|
+
},
|
|
15
|
+
"bugs": {
|
|
16
|
+
"url": "https://github.com/fyeeme/pi-packages/issues"
|
|
17
|
+
},
|
|
11
18
|
"homepage": "https://github.com/fyeeme/pi-packages#readme",
|
|
12
|
-
"engines": {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
"
|
|
16
|
-
|
|
19
|
+
"engines": {
|
|
20
|
+
"node": ">=18"
|
|
21
|
+
},
|
|
22
|
+
"keywords": [
|
|
23
|
+
"pi-package",
|
|
24
|
+
"pi",
|
|
25
|
+
"session",
|
|
26
|
+
"auto-name",
|
|
27
|
+
"rename",
|
|
28
|
+
"resume"
|
|
29
|
+
],
|
|
30
|
+
"files": [
|
|
31
|
+
"index.ts",
|
|
32
|
+
"README.md",
|
|
33
|
+
"LICENSE",
|
|
34
|
+
"CHANGELOG.md"
|
|
35
|
+
],
|
|
36
|
+
"pi": {
|
|
37
|
+
"extensions": [
|
|
38
|
+
"./index.ts"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"scripts": {
|
|
42
|
+
"test": "vitest --run",
|
|
43
|
+
"typecheck": "tsc"
|
|
44
|
+
},
|
|
17
45
|
"peerDependencies": {
|
|
18
|
-
"@earendil-works/pi-coding-agent": ">=0.
|
|
19
|
-
"@earendil-works/pi-ai": ">=0.
|
|
46
|
+
"@earendil-works/pi-coding-agent": ">=0.84.1",
|
|
47
|
+
"@earendil-works/pi-ai": ">=0.84.1"
|
|
20
48
|
},
|
|
21
49
|
"devDependencies": {
|
|
22
|
-
"@earendil-works/pi-coding-agent": "0.
|
|
23
|
-
"@earendil-works/pi-ai": "0.
|
|
50
|
+
"@earendil-works/pi-coding-agent": "0.84.1",
|
|
51
|
+
"@earendil-works/pi-ai": "0.84.1",
|
|
24
52
|
"@types/node": "22.19.19",
|
|
25
53
|
"typescript": "5.9.3",
|
|
26
54
|
"vitest": "3.2.7"
|