@henryqw/pi-prompt-creator 3.1.3 → 3.1.5
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/CONTEXT.md +1 -0
- package/README.md +23 -21
- package/extensions/prompt-creator.ts +27 -36
- package/package.json +3 -3
package/CONTEXT.md
CHANGED
|
@@ -19,6 +19,7 @@ Turn repetition and correction in the current Pi conversation into one editable
|
|
|
19
19
|
| --- | --- |
|
|
20
20
|
| Cross-session evidence and historical search | `@henryqw/pi-session-recall` |
|
|
21
21
|
| Scripts, Skills, and other automation | Their owning package or repository |
|
|
22
|
+
| User config load rules and launch reporting | `@henryqw/pi-config-store` ([ADR 004](../../docs/adr/004-extension-config-safety.md)) |
|
|
22
23
|
| This extension | Current-conversation Signal detection, Prompt Candidate generation, and Prompt Review |
|
|
23
24
|
|
|
24
25
|
The extension stays silent when it finds no Prompt Candidate. Background analysis remains visible while it is running. Manually requested candidates display when ready; automatic candidates wait for explicit user display, so background work never steals editor focus.
|
package/README.md
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# `@henryqw/pi-prompt-creator`
|
|
2
2
|
|
|
3
|
-
Turn repeated requests or corrections in the current conversation into a reviewed global Pi prompt. A tool-free child drafts one
|
|
3
|
+
Turn repeated requests or corrections in the current conversation into a reviewed global Pi prompt. A tool-free child drafts one Prompt Candidate. Main and the user control Prompt Review and the save.
|
|
4
4
|
|
|
5
|
-
Automatic analysis only prepares a candidate
|
|
5
|
+
Automatic analysis only prepares a candidate. The extension never saves it without your approval.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
This package requires Pi 1.1.0 or later in the 1.x series. Older Pi releases are no longer supported.
|
|
10
10
|
|
|
11
11
|
```bash
|
|
12
12
|
pi install npm:@henryqw/pi-task-models
|
|
@@ -29,25 +29,25 @@ Run `/promptor` in the interactive TUI after a repeated request or correction ap
|
|
|
29
29
|
2. Ask Main to refine it and return only the complete Final Prompt Draft.
|
|
30
30
|
3. Run `/promptor save` to save under the suggested name, or `/promptor save <name>` to rename it.
|
|
31
31
|
|
|
32
|
-
The prompt is created at `~/.pi/agent/prompts/<name>.md
|
|
32
|
+
The prompt is created at `~/.pi/agent/prompts/<name>.md`. Then Pi reloads its resources. No selection menu or name prompt is required.
|
|
33
33
|
|
|
34
34
|
| Surface | Type | Purpose |
|
|
35
35
|
| --- | --- | --- |
|
|
36
36
|
| `/promptor [analyze|dismiss|save [name]|automatic <on\|off>]` | command | For users in the interactive TUI: start or show analysis, dismiss a pending candidate, save a reviewed prompt, or control automatic analysis. |
|
|
37
|
-
| `pi-prompt-creator/draft` | task | For users
|
|
38
|
-
| Analysis status widget and candidate message | ui | Show analysis progress, readiness, or failure
|
|
37
|
+
| `pi-prompt-creator/draft` | task | For users who configure `/task-models`: choose the child model and thinking level for the draft. |
|
|
38
|
+
| Analysis status widget and candidate message | ui | Show analysis progress, readiness, or failure. A displayed candidate is marked as untrusted. |
|
|
39
39
|
|
|
40
40
|
## Flow
|
|
41
41
|
|
|
42
42
|

|
|
43
43
|
|
|
44
|
-
The diagram shows the automatic path. Manual analysis displays its candidate immediately, without the WAIT stage. Both paths require Main review and your approval before
|
|
44
|
+
The diagram shows the automatic path. Manual analysis displays its candidate immediately, without the WAIT stage. Both paths require Main review and your approval before the save.
|
|
45
45
|
|
|
46
46
|
### Automatic analysis
|
|
47
47
|
|
|
48
48
|
Automatic mode waits for the configured number of non-empty user inputs. The default is three.
|
|
49
49
|
|
|
50
|
-
It starts at the next idle `agent_settled` event that is not aborted. It starts at most
|
|
50
|
+
It starts at the next idle `agent_settled` event that is not aborted. It starts at most one time per extension runtime.
|
|
51
51
|
|
|
52
52
|
An aborted run does not start automatic analysis. It keeps the input count and the automatic opportunity, so a later eligible normal settlement can start analysis. `aborted: false` does not prove that the provider completed successfully.
|
|
53
53
|
|
|
@@ -57,7 +57,7 @@ Branch changes reset the input counter. Automatic analysis and candidate widgets
|
|
|
57
57
|
|
|
58
58
|
Only one analysis can run at a time. A pending candidate blocks another analysis.
|
|
59
59
|
|
|
60
|
-
Manual and automatic analysis send Pi's current model-visible session context to the child model configured through `/task-models`. Session context edits that omit or replace messages are honored
|
|
60
|
+
Manual and automatic analysis send Pi's current model-visible session context to the child model configured through `/task-models`. Session context edits that omit or replace messages are honored. Original text removed from model context is not sent to the child. Request-local `context` and `context_with_system` transformations are not included.
|
|
61
61
|
|
|
62
62
|
Its payload includes the active compaction or branch summary, user text, successfully completed assistant text, and effective prompt names and descriptions.
|
|
63
63
|
|
|
@@ -69,17 +69,17 @@ New user input does not stop a running child. Branch navigation discards its old
|
|
|
69
69
|
|
|
70
70
|
### Review and save
|
|
71
71
|
|
|
72
|
-
A manually requested analysis displays its completed candidate immediately. Automatic analysis shows a running widget
|
|
72
|
+
A manually requested analysis displays its completed candidate immediately. Automatic analysis shows a running widget. Then it keeps a completed candidate behind the `Prompt ready — /promptor` widget until you run `/promptor` or `/promptor dismiss`. Thus, background work never interrupts the conversation.
|
|
73
73
|
|
|
74
74
|
Displayed candidates are visible messages marked as untrusted.
|
|
75
75
|
|
|
76
|
-
Refine the candidate with Main. Ask Main to return only the complete Final Prompt Draft
|
|
76
|
+
Refine the candidate with Main. Ask Main to return only the complete Final Prompt Draft. Then save it with `/promptor save [name]`.
|
|
77
77
|
|
|
78
|
-
|
|
78
|
+
You can save only after you show a candidate and Main then completes a valid Markdown review reply. The save uses that latest retained Main reply as the entire file. Replies before the active compaction or branch summary cannot be saved.
|
|
79
79
|
|
|
80
80
|
An interrupted, failed, empty, or tool-use reply cannot be saved. The extension never falls back to an older reply.
|
|
81
81
|
|
|
82
|
-
A successful save ends that review. Show another candidate and complete another Main review reply before
|
|
82
|
+
A successful save ends that review. Show another candidate and complete another Main review reply before you save again.
|
|
83
83
|
|
|
84
84
|
`/promptor save` uses the suggested candidate name. Pass `/promptor save <name>` to choose another. Names must be lowercase kebab-case, start with a letter, and contain at most 64 ASCII characters. Existing command names and prompt files are rejected.
|
|
85
85
|
|
|
@@ -89,7 +89,7 @@ A successful save reloads Pi resources. If reload fails, the prompt remains save
|
|
|
89
89
|
|
|
90
90
|
The extension registers `pi-prompt-creator/draft`. Its default task profile is `fast`.
|
|
91
91
|
|
|
92
|
-
Use `/task-models` to select the model and thinking level. The extension owns no model configuration.
|
|
92
|
+
Use `/task-models` to select the model and thinking level. The extension owns no model configuration. At session start and before each analysis, it shows each problem in the shared task-model config one time in each session.
|
|
93
93
|
|
|
94
94
|
## Config
|
|
95
95
|
|
|
@@ -100,27 +100,29 @@ Package-owned: `~/.pi/agent/config/pi-prompt-creator/config.json`
|
|
|
100
100
|
| `automatic` | Runs automatic analysis after enough user inputs. | Boolean. | `true` |
|
|
101
101
|
| `inputThreshold` | Sets the number of non-empty user inputs before automatic analysis. | Positive safe integer (`1`–`9007199254740991`). | `3` |
|
|
102
102
|
|
|
103
|
-
Edit either field
|
|
103
|
+
Edit either field. Then run `/reload` to apply the change. Omitted fields use their defaults.
|
|
104
104
|
|
|
105
105
|
A missing file quietly uses both defaults. Startup never creates or rewrites the file.
|
|
106
106
|
|
|
107
|
-
|
|
107
|
+
At session start, Pi shows a warning with the file path and each problem for an unknown key or an invalid value. Pi ignores the unknown key. A misspelled key near a missing field, such as `automatc`, counts as an invalid value of that field. An invalid `inputThreshold` uses the default `3`.
|
|
108
108
|
|
|
109
|
-
|
|
109
|
+
Automatic analysis stays off for the session when `automatic` is invalid or misspelled, or when the file has malformed JSON, invalid UTF-8, more than 64 KiB, a read error, or a non-object root. Startup leaves the file unchanged.
|
|
110
|
+
|
|
111
|
+
Only `/promptor automatic on` or `/promptor automatic off` writes the config. A toggle reads the file again and keeps its current `inputThreshold`, also when another session changed it. A toggle also replaces a file with problems: it first shows the problems, then keeps the usable values and removes unknown keys. If another session changes that file before the write, the toggle stops and writes nothing.
|
|
110
112
|
|
|
111
113
|
## State and storage
|
|
112
114
|
|
|
113
115
|
A pending candidate stays in memory only. Automatic mode persists, but counters and candidates do not.
|
|
114
116
|
|
|
115
|
-
Saved prompts
|
|
117
|
+
Saved prompts are at `~/.pi/agent/prompts/<name>.md`. A save never replaces an existing file.
|
|
116
118
|
|
|
117
119
|
## Limits and recovery
|
|
118
120
|
|
|
119
121
|
The child payload stays within 30,000 characters, including its JSON envelope and prompt metadata.
|
|
120
122
|
|
|
121
|
-
It
|
|
123
|
+
It gives priority to the active summary, then to the newest complete messages. It omits excess prompt metadata before conversation context.
|
|
122
124
|
|
|
123
|
-
Old messages
|
|
125
|
+
Old messages can be omitted. Individual messages are never cut.
|
|
124
126
|
|
|
125
127
|
Generated candidate names use lowercase kebab-case and stay within 64 ASCII characters.
|
|
126
128
|
|
|
@@ -128,7 +130,7 @@ Candidate and Final Prompt Draft Markdown must be non-empty and at most 16 KiB.
|
|
|
128
130
|
|
|
129
131
|
Candidate and saved Markdown reject NUL and other terminal controls. Ordinary tabs and line feeds are allowed.
|
|
130
132
|
|
|
131
|
-
|
|
133
|
+
If analysis finds no candidate, the running widget clears without a notice. Invalid output or child failure shows `Prompt analysis failed — /promptor`. Failed analysis is not retried automatically. Run `/promptor` or `/promptor analyze` to start again.
|
|
132
134
|
|
|
133
135
|
The failure widget clears after the next non-empty user input. Analysis has a two-minute idle timeout and five-minute hard timeout.
|
|
134
136
|
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
type ExtensionContext,
|
|
11
11
|
type SessionEntry,
|
|
12
12
|
} from "@earendil-works/pi-coding-agent";
|
|
13
|
-
import { createConfigStore } from "@henryqw/pi-config-store";
|
|
13
|
+
import { createConfigStore, field, parsers, registerConfigReporting } from "@henryqw/pi-config-store";
|
|
14
14
|
import {
|
|
15
15
|
createEphemeralSubagentExecutor,
|
|
16
16
|
resolveRoleLaunch,
|
|
@@ -19,6 +19,7 @@ import {
|
|
|
19
19
|
} from "@henryqw/pi-subagent";
|
|
20
20
|
import {
|
|
21
21
|
registerModelTask,
|
|
22
|
+
registerTaskModelsConfigReporting,
|
|
22
23
|
type ModelTask,
|
|
23
24
|
} from "@henryqw/pi-task-models";
|
|
24
25
|
|
|
@@ -60,7 +61,6 @@ or
|
|
|
60
61
|
The object must have exactly these keys. name must start with a lowercase ASCII letter, use only lowercase ASCII letters, digits, and single hyphens, and be at most ${MAX_NAME_CHARS} characters. markdown must be nonempty, at most ${MAX_MARKDOWN_BYTES} UTF-8 bytes, and contain no C0 or C1 controls except tab and LF.`,
|
|
61
62
|
} satisfies Role;
|
|
62
63
|
|
|
63
|
-
type Config = { automatic: boolean; inputThreshold: number };
|
|
64
64
|
type PromptCandidate = { name: string; markdown: string };
|
|
65
65
|
type ConversationItem = { role: "summary" | "user" | "assistant"; text: string };
|
|
66
66
|
type ContextMessage = ReturnType<typeof buildSessionContext>["messages"][number];
|
|
@@ -80,21 +80,6 @@ function exactKeys(value: Record<string, unknown>, keys: readonly string[]): boo
|
|
|
80
80
|
return actual.length === keys.length && keys.every((key) => Object.hasOwn(value, key));
|
|
81
81
|
}
|
|
82
82
|
|
|
83
|
-
function parseConfig(value: unknown): Config {
|
|
84
|
-
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("Config must be an object.");
|
|
85
|
-
const config = value as Record<string, unknown>;
|
|
86
|
-
const validKeys = Object.keys(config).every((key) => key === "automatic" || key === "inputThreshold");
|
|
87
|
-
const { automatic = true, inputThreshold = DEFAULT_INPUT_THRESHOLD } = config;
|
|
88
|
-
if (
|
|
89
|
-
!validKeys
|
|
90
|
-
|| typeof automatic !== "boolean"
|
|
91
|
-
|| typeof inputThreshold !== "number"
|
|
92
|
-
|| !Number.isSafeInteger(inputThreshold)
|
|
93
|
-
|| inputThreshold < 1
|
|
94
|
-
) throw new Error("Config may contain automatic:boolean and inputThreshold:positive integer.");
|
|
95
|
-
return { automatic, inputThreshold };
|
|
96
|
-
}
|
|
97
|
-
|
|
98
83
|
function isPromptName(value: unknown): value is string {
|
|
99
84
|
return typeof value === "string" && value.length <= MAX_NAME_CHARS && NAME.test(value);
|
|
100
85
|
}
|
|
@@ -253,17 +238,20 @@ async function createPromptFile(path: string, markdown: string): Promise<void> {
|
|
|
253
238
|
export default function promptCreatorExtension(pi: ExtensionAPI, options: PromptCreatorOptions = {}): void {
|
|
254
239
|
registerModelTask(pi, DRAFT_TASK);
|
|
255
240
|
const agentDir = options.agentDir ?? getAgentDir();
|
|
256
|
-
const configStore = createConfigStore
|
|
241
|
+
const configStore = createConfigStore({
|
|
257
242
|
extensionId: EXTENSION_ID,
|
|
258
243
|
agentDir,
|
|
259
|
-
|
|
260
|
-
|
|
244
|
+
fields: {
|
|
245
|
+
automatic: field(parsers.boolean(), true),
|
|
246
|
+
inputThreshold: field(parsers.integer({ min: 1 }), DEFAULT_INPUT_THRESHOLD),
|
|
247
|
+
},
|
|
261
248
|
});
|
|
249
|
+
const configReporter = registerConfigReporting(pi, [configStore]);
|
|
250
|
+
const reportTaskModels = registerTaskModelsConfigReporting(pi, agentDir);
|
|
262
251
|
let executor = options.executor;
|
|
263
252
|
let automatic = false;
|
|
264
253
|
let inputThreshold = DEFAULT_INPUT_THRESHOLD;
|
|
265
254
|
let automaticConsumed = false;
|
|
266
|
-
let configWarned = false;
|
|
267
255
|
let inputCount = 0;
|
|
268
256
|
let branchGeneration = 0;
|
|
269
257
|
let candidate: PromptCandidate | undefined;
|
|
@@ -328,6 +316,7 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
|
|
|
328
316
|
void Promise.resolve().then(() => getExecutor().run({
|
|
329
317
|
signal: run.controller.signal,
|
|
330
318
|
prepare: async () => {
|
|
319
|
+
reportTaskModels(ctx);
|
|
331
320
|
const launch = resolveRoleLaunch(pi, ctx, {
|
|
332
321
|
role: DRAFT_ROLE,
|
|
333
322
|
task: DRAFT_TASK,
|
|
@@ -397,18 +386,11 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
|
|
|
397
386
|
activeRun?.controller.abort(new Error("Prompt Creator session changed."));
|
|
398
387
|
activeRun = undefined;
|
|
399
388
|
resetBranch(ctx);
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
automatic = false;
|
|
406
|
-
inputThreshold = DEFAULT_INPUT_THRESHOLD;
|
|
407
|
-
if (!configWarned) {
|
|
408
|
-
configWarned = true;
|
|
409
|
-
ctx.ui.notify("Prompt Creator config is invalid. Automatic analysis is disabled; the file was left unchanged.", "warning");
|
|
410
|
-
}
|
|
411
|
-
}
|
|
389
|
+
const loaded = configStore.loadSync();
|
|
390
|
+
configReporter.report(ctx, configStore, loaded);
|
|
391
|
+
// A file-level problem or an invalid or misspelled `automatic` keeps automatic analysis off; a missing file uses the default.
|
|
392
|
+
automatic = loaded.status === "ok" && loaded.value.automatic && !loaded.issues.some(({ key }) => key === "" || key === "automatic");
|
|
393
|
+
inputThreshold = loaded.status === "ok" ? loaded.value.inputThreshold : DEFAULT_INPUT_THRESHOLD;
|
|
412
394
|
});
|
|
413
395
|
|
|
414
396
|
pi.on("input", (event, ctx) => {
|
|
@@ -491,11 +473,20 @@ export default function promptCreatorExtension(pi: ExtensionAPI, options: Prompt
|
|
|
491
473
|
if (action === "automatic" && values.length === 1 && (values[0] === "on" || values[0] === "off")) {
|
|
492
474
|
const next = values[0] === "on";
|
|
493
475
|
try {
|
|
494
|
-
|
|
476
|
+
// Write the latest file, not the session's cached threshold, and report its problems first.
|
|
477
|
+
const loaded = configStore.loadSync();
|
|
478
|
+
configReporter.report(ctx, configStore, loaded);
|
|
479
|
+
// No field disables this config, so a load always has a value.
|
|
480
|
+
if (loaded.status !== "ok") return;
|
|
481
|
+
let written = { ...loaded.value, automatic: next };
|
|
482
|
+
if (!loaded.issues.length) written = await configStore.update((latest) => ({ ...latest, automatic: next }));
|
|
483
|
+
// A file with problems is replaced with its usable values, and only if no other session changed it meanwhile.
|
|
484
|
+
else await configStore.replaceIfUnchanged(loaded, written);
|
|
495
485
|
automatic = next;
|
|
486
|
+
inputThreshold = written.inputThreshold;
|
|
496
487
|
ctx.ui.notify(`Automatic analysis ${next ? "enabled" : "disabled"}.`, "info");
|
|
497
|
-
} catch {
|
|
498
|
-
ctx.ui.notify(
|
|
488
|
+
} catch (error) {
|
|
489
|
+
ctx.ui.notify(`Could not save Prompt Creator config: ${error instanceof Error ? error.message : String(error)}`, "error");
|
|
499
490
|
}
|
|
500
491
|
return;
|
|
501
492
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@henryqw/pi-prompt-creator",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.5",
|
|
4
4
|
"description": "Turn current Pi conversation signals into user-approved prompt templates.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"pi-package",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
]
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@henryqw/pi-config-store": "^
|
|
48
|
+
"@henryqw/pi-config-store": "^4.0.0",
|
|
49
49
|
"@henryqw/pi-subagent": "^22.0.0 || ^23.0.0 || ^24.0.0 || ^25.0.0 || ^26.0.0 || ^27.0.0",
|
|
50
|
-
"@henryqw/pi-task-models": "^
|
|
50
|
+
"@henryqw/pi-task-models": "^9.0.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@earendil-works/pi-coding-agent": "1.1.0"
|