pi-note 0.8.0 → 0.9.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/README.md +23 -9
- package/index.ts +36 -6
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -81,20 +81,34 @@ worktrees and pull requests. Task status refreshes at session start, before/afte
|
|
|
81
81
|
agent activity, and after commands/tools. The CLI's per-directory `agent selected`
|
|
82
82
|
command is not this session memory.
|
|
83
83
|
|
|
84
|
-
###
|
|
84
|
+
### Personal configuration
|
|
85
85
|
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
Configure `~/.pi/agent/pi-note.json` (or the agent directory set by
|
|
87
|
+
`PI_CODING_AGENT_DIR`):
|
|
88
88
|
|
|
89
89
|
```json
|
|
90
90
|
{
|
|
91
|
-
"handoffPrompt": "Read the current Pinote task. Summarize your understanding and proposed approach, but don’t start work yet."
|
|
91
|
+
"handoffPrompt": "Read the current Pinote task. Summarize your understanding and proposed approach, but don’t start work yet.",
|
|
92
|
+
"taskOfferPolicy": "github-remote"
|
|
92
93
|
}
|
|
93
94
|
```
|
|
94
95
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
96
|
+
`taskOfferPolicy` controls the agent's task-offer guidance:
|
|
97
|
+
|
|
98
|
+
- `always` (default): offer a task when given work with no task selected.
|
|
99
|
+
- `github-remote`: first verify with Git that the repository has an HTTPS or SSH
|
|
100
|
+
remote hosted on `github.com`; otherwise do not offer. Local paths and other
|
|
101
|
+
hosts do not qualify. This is agent guidance, not a network or PR-access check.
|
|
102
|
+
- `never`: do not offer tasks; explicit requests to create one remain allowed.
|
|
103
|
+
|
|
104
|
+
All policies preserve the existing creation tools, user-consent requirement,
|
|
105
|
+
and selected tasks. Run `/reload` after changing this setting. Invalid policy
|
|
106
|
+
values or unreadable/malformed configuration suppress offers; interactive startup
|
|
107
|
+
warns until you fix the file and reload. No project-local configuration is read.
|
|
108
|
+
|
|
109
|
+
Task selection and **Continue** reread `handoffPrompt` each time; changing that
|
|
110
|
+
field needs no `/reload`. Its value is literal text, not a prompt template; use
|
|
111
|
+
`\n` in JSON strings for multiple lines. It must be a nonblank string without control
|
|
98
112
|
characters other than tabs and newlines. A missing file or key uses the default.
|
|
99
113
|
Invalid/unreadable configuration reports an error without starting/switching a
|
|
100
114
|
task or changing the editor. **Done** and agent tools are unaffected. This is
|
|
@@ -117,8 +131,8 @@ Version 0.7.0 replaces `pinote_get`/`pinote_update` with these current-task tool
|
|
|
117
131
|
and removes `pinote_tag`, without aliases. Agents cannot read/update tasks by ID
|
|
118
132
|
or retag existing tasks; use `/pi-note` to select an existing task.
|
|
119
133
|
|
|
120
|
-
When
|
|
121
|
-
|
|
134
|
+
When allowed by `taskOfferPolicy`, the agent proposes one note as `[tag] text`
|
|
135
|
+
and asks before creating it. No continues without a note. Yes calls
|
|
122
136
|
`pinote_add` with `select: true`. An existing selection is not replaced unless the
|
|
123
137
|
user asks to switch. Reuse a saved tag name when it fits.
|
|
124
138
|
|
package/index.ts
CHANGED
|
@@ -9,10 +9,13 @@ import { TaskPicker, taskState, taskTag } from "./task-picker.ts";
|
|
|
9
9
|
import { bundledCLIVersion, cliMenu, detectCLI, setupCLI, setupHint, versionAtLeast, type CLIAction } from "./setup.ts";
|
|
10
10
|
|
|
11
11
|
const handoffGuidance = "Keep agent notes to at most three short bullets total: outcome, blocker, next action, only when relevant. Replace stale notes; omit narration, repeated task text, and routine test logs. Keep a GitHub pull request in the PR field. To show another footer field, set its Markdown value and append its label to Bar, one label per line. Links in that text are clickable. Do not list PR in Bar. Remove the field and its Bar line to drop it. Notes stay off the footer unless named in Bar.";
|
|
12
|
-
const
|
|
13
|
-
"When the user gives work and no pinote task is selected, propose one note as `[tag] text` and ask before creating it.
|
|
14
|
-
"
|
|
15
|
-
|
|
12
|
+
const offerGuidance = {
|
|
13
|
+
always: "When the user gives work and no pinote task is selected, propose one note as `[tag] text` and ask before creating it.",
|
|
14
|
+
"github-remote": "When the user gives work and no pinote task is selected, first verify with Git that the current repository has a remote whose URL host is github.com (HTTPS or SSH). Only then propose one note as `[tag] text` and ask before creating it. Local paths, other hosts, and GitHub-looking URL paths do not qualify. If no GitHub remote is verified, do not offer a task; explicit user requests to create one remain allowed.",
|
|
15
|
+
never: "Do not offer to create a pinote task. Create one only when the user explicitly requests it.",
|
|
16
|
+
};
|
|
17
|
+
const consentGuidance = "On no, continue without a note. On yes, call pinote_add with select true so it becomes this session's active task. Never add or select without a yes. If a task is already selected, do not replace it unless the user asks to switch.";
|
|
18
|
+
const tagGuidance = "Reuse a pinote_tags name when it fits. One tag; case-sensitive, trimmed, at most 64 characters. Tags are set when creating tasks with pinote_add; agent tools cannot retag existing tasks. Do not use pinote_update_current for tags or task text.";
|
|
16
19
|
type Task = {
|
|
17
20
|
id: number;
|
|
18
21
|
text: string;
|
|
@@ -36,18 +39,43 @@ const firstLine = (task: Summary) => clean(task.text.split("\n", 1)[0]).replace(
|
|
|
36
39
|
const title = (task: Summary) => truncateToWidth(`#${task.id} ${firstLine(task)}`, 60);
|
|
37
40
|
|
|
38
41
|
const defaultHandoffPrompt = "Read the current Pinote task. Summarize your understanding, but don’t start work yet.";
|
|
39
|
-
function
|
|
42
|
+
function readConfig(): Record<string, unknown> {
|
|
40
43
|
const path = join(getAgentDir(), "pi-note.json");
|
|
41
44
|
let raw: string;
|
|
42
45
|
try {
|
|
43
46
|
raw = readFileSync(path, "utf8");
|
|
44
47
|
} catch (error) {
|
|
45
|
-
if ((error as NodeJS.ErrnoException).code === "ENOENT") return
|
|
48
|
+
if ((error as NodeJS.ErrnoException).code === "ENOENT") return {};
|
|
46
49
|
throw new Error(`Cannot read ${path}. Check the pi-note configuration.`);
|
|
47
50
|
}
|
|
48
51
|
let config: unknown;
|
|
49
52
|
try { config = JSON.parse(raw); } catch { throw new Error(`Invalid JSON in ${path}.`); }
|
|
50
53
|
if (!record(config)) throw new Error(`Expected a JSON object in ${path}.`);
|
|
54
|
+
return config;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function creationGuidance(): { guidelines: string[]; error?: string } {
|
|
58
|
+
let policy: keyof typeof offerGuidance = "always";
|
|
59
|
+
let error: string | undefined;
|
|
60
|
+
try {
|
|
61
|
+
const config = readConfig();
|
|
62
|
+
if ("taskOfferPolicy" in config) {
|
|
63
|
+
const value = config.taskOfferPolicy;
|
|
64
|
+
if (value !== "always" && value !== "github-remote" && value !== "never") {
|
|
65
|
+
throw new Error(`taskOfferPolicy in ${join(getAgentDir(), "pi-note.json")} must be always, github-remote, or never.`);
|
|
66
|
+
}
|
|
67
|
+
policy = value;
|
|
68
|
+
}
|
|
69
|
+
} catch (cause) {
|
|
70
|
+
policy = "never";
|
|
71
|
+
error = cause instanceof Error ? cause.message : String(cause);
|
|
72
|
+
}
|
|
73
|
+
return { guidelines: [`${offerGuidance[policy]} ${consentGuidance}`, tagGuidance], error };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function handoffPrompt(): string {
|
|
77
|
+
const config = readConfig();
|
|
78
|
+
const path = join(getAgentDir(), "pi-note.json");
|
|
51
79
|
if (!("handoffPrompt" in config)) return defaultHandoffPrompt;
|
|
52
80
|
if (!text(config.handoffPrompt) || !config.handoffPrompt.trim()) {
|
|
53
81
|
throw new Error(`handoffPrompt in ${path} must be a nonblank string without control characters (except tabs/newlines).`);
|
|
@@ -90,6 +118,7 @@ function toolResult(value: Task | null) {
|
|
|
90
118
|
}
|
|
91
119
|
|
|
92
120
|
export default function (pi: ExtensionAPI) {
|
|
121
|
+
const { guidelines: createGuidance, error: offerConfigError } = creationGuidance();
|
|
93
122
|
let alive = true;
|
|
94
123
|
let epoch = 0;
|
|
95
124
|
let refreshSerial = 0;
|
|
@@ -248,6 +277,7 @@ export default function (pi: ExtensionAPI) {
|
|
|
248
277
|
const generation = epoch;
|
|
249
278
|
cliState = undefined;
|
|
250
279
|
if (ctx.hasUI && ctx.mode === "tui") {
|
|
280
|
+
if (offerConfigError) ctx.ui.notify(`Pinote task offers disabled: ${clean(offerConfigError)} Fix the configuration and /reload.`, "warning");
|
|
251
281
|
ctx.ui.addAutocompleteProvider((current) => cliMenu(current, () => setupAbort ? undefined : cliState));
|
|
252
282
|
}
|
|
253
283
|
// Keep the setup lock until its aborted subprocess has actually settled.
|