@stfade/pi-read-delegator 1.0.11 → 1.0.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/config.d.ts +1 -0
- package/config.js +8 -3
- package/index.js +79 -24
- package/package.json +1 -1
- package/reader-manager.js +35 -0
- package/tool-blocker.js +7 -6
package/config.d.ts
CHANGED
package/config.js
CHANGED
|
@@ -45,6 +45,7 @@ exports.saveConfig = saveConfig;
|
|
|
45
45
|
const fs = __importStar(require("fs"));
|
|
46
46
|
const os = __importStar(require("os"));
|
|
47
47
|
const path = __importStar(require("path"));
|
|
48
|
+
const ui_1 = require("./ui");
|
|
48
49
|
// ---------------------------------------------------------------------------
|
|
49
50
|
// Defaults
|
|
50
51
|
// ---------------------------------------------------------------------------
|
|
@@ -62,6 +63,7 @@ const DEFAULT_CONFIG = {
|
|
|
62
63
|
"cp",
|
|
63
64
|
],
|
|
64
65
|
orchestrator_prompt: "You are an orchestrator. For any file reading, searching, or listing operation, you MUST use the subagent tool with subagent='reader'. Do not use read/grep/find/ls yourself. If you need to run a shell command that only reads (like cat, grep, find, ls), also delegate it to the reader subagent.",
|
|
66
|
+
reader_model: "lmstudio/nvidia/nemotron-3-nano-4b",
|
|
65
67
|
language: "auto",
|
|
66
68
|
};
|
|
67
69
|
// ---------------------------------------------------------------------------
|
|
@@ -104,7 +106,7 @@ function loadConfig() {
|
|
|
104
106
|
}
|
|
105
107
|
catch (err) {
|
|
106
108
|
// File is missing, unreadable, or invalid JSON → overwrite with defaults
|
|
107
|
-
|
|
109
|
+
(0, ui_1.rawWarn)(`Corrupted config file at ${filePath}. Overwriting with defaults. Error: ${err}`);
|
|
108
110
|
try {
|
|
109
111
|
ensureDir(path.dirname(filePath));
|
|
110
112
|
fs.writeFileSync(filePath, JSON.stringify(DEFAULT_CONFIG, null, 2), "utf-8");
|
|
@@ -126,11 +128,11 @@ function saveConfig(config, options) {
|
|
|
126
128
|
try {
|
|
127
129
|
fs.writeFileSync(filePath, JSON.stringify(config, null, 2), "utf-8");
|
|
128
130
|
if (!options?.silent) {
|
|
129
|
-
|
|
131
|
+
(0, ui_1.rawLog)(`Config saved to ${filePath}`);
|
|
130
132
|
}
|
|
131
133
|
}
|
|
132
134
|
catch (err) {
|
|
133
|
-
|
|
135
|
+
(0, ui_1.rawError)(`Failed to save config: ${err}`);
|
|
134
136
|
throw err;
|
|
135
137
|
}
|
|
136
138
|
}
|
|
@@ -157,6 +159,9 @@ function mergeDefaults(partial, defaults) {
|
|
|
157
159
|
orchestrator_prompt: typeof p.orchestrator_prompt === "string"
|
|
158
160
|
? p.orchestrator_prompt
|
|
159
161
|
: defaults.orchestrator_prompt,
|
|
162
|
+
reader_model: typeof p.reader_model === "string"
|
|
163
|
+
? p.reader_model
|
|
164
|
+
: defaults.reader_model,
|
|
160
165
|
language: typeof p.language === "string" ? p.language : defaults.language,
|
|
161
166
|
};
|
|
162
167
|
}
|
package/index.js
CHANGED
|
@@ -85,32 +85,64 @@ const READ_BASH_COMMANDS = new Set([
|
|
|
85
85
|
function readerPath() {
|
|
86
86
|
return path.join(os.homedir(), ".pi", "agent", "agents", "reader.md");
|
|
87
87
|
}
|
|
88
|
-
|
|
88
|
+
function syncReaderTemplate(model) {
|
|
89
89
|
const rp = readerPath();
|
|
90
|
-
if (fs.existsSync(rp))
|
|
91
|
-
return;
|
|
92
|
-
const content = [
|
|
93
|
-
"---",
|
|
94
|
-
"name: reader",
|
|
95
|
-
"description: Token-efficient code reader that returns minimal results.",
|
|
96
|
-
"tools: read, grep, find, ls",
|
|
97
|
-
"model: lmstudio/nvidia/nemotron-3-nano-4b",
|
|
98
|
-
"---",
|
|
99
|
-
"",
|
|
100
|
-
"You are a token-efficient analyst. Execute read/search/list tasks and return",
|
|
101
|
-
"ONLY the essential result. Maximum 10 lines. Use bullet summaries.",
|
|
102
|
-
"Never dump entire files. Focus only on what was asked.",
|
|
103
|
-
].join("\n");
|
|
104
90
|
try {
|
|
105
91
|
const dir = path.dirname(rp);
|
|
106
92
|
fs.mkdirSync(dir, { recursive: true });
|
|
107
|
-
|
|
93
|
+
const content = [
|
|
94
|
+
"---",
|
|
95
|
+
"name: reader",
|
|
96
|
+
"description: Token-efficient code reader that returns minimal results.",
|
|
97
|
+
"tools: read, grep, find, ls",
|
|
98
|
+
`model: ${model}`,
|
|
99
|
+
"---",
|
|
100
|
+
"",
|
|
101
|
+
"You are a token-efficient analyst. Execute read/search/list tasks and return",
|
|
102
|
+
"ONLY the essential result. Maximum 10 lines. Use bullet summaries.",
|
|
103
|
+
"Never dump entire files. Focus only on what was asked.",
|
|
104
|
+
].join("\n");
|
|
105
|
+
fs.writeFileSync(rp, content, "utf8");
|
|
108
106
|
}
|
|
109
107
|
catch {
|
|
110
|
-
// read-only home directory — template
|
|
108
|
+
// read-only home directory — template sync is best-effort
|
|
111
109
|
}
|
|
112
110
|
}
|
|
113
111
|
// ---------------------------------------------------------------------------
|
|
112
|
+
// Model picker
|
|
113
|
+
// ---------------------------------------------------------------------------
|
|
114
|
+
/**
|
|
115
|
+
* Interactive model picker using Pi's model registry.
|
|
116
|
+
* Shows a select UI with all configured models and updates config + reader.md
|
|
117
|
+
* when the user picks a new model.
|
|
118
|
+
*
|
|
119
|
+
* Returns the selected model string ("provider/model") or undefined if the
|
|
120
|
+
* picker is unavailable or the user cancels.
|
|
121
|
+
*/
|
|
122
|
+
async function pickReaderModel(config, ctx) {
|
|
123
|
+
try {
|
|
124
|
+
const models = ctx.modelRegistry?.getAvailable?.() ?? [];
|
|
125
|
+
if (models.length === 0)
|
|
126
|
+
return undefined;
|
|
127
|
+
const options = models.map((m) => `${m.provider ?? "?"}/${m.id}`);
|
|
128
|
+
if (!options.includes(config.reader_model)) {
|
|
129
|
+
options.unshift(config.reader_model);
|
|
130
|
+
}
|
|
131
|
+
const selected = await ctx.ui.select("Choose reader model (ESC to keep current)", options);
|
|
132
|
+
if (selected && selected !== config.reader_model) {
|
|
133
|
+
config.reader_model = selected;
|
|
134
|
+
(0, config_1.saveConfig)(config, { silent: true });
|
|
135
|
+
syncReaderTemplate(selected);
|
|
136
|
+
ctx.ui.notify("✅ Reader model set to: " + selected, "info");
|
|
137
|
+
return selected;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
catch {
|
|
141
|
+
// modelRegistry or ui.select not available — fallback
|
|
142
|
+
}
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
// ---------------------------------------------------------------------------
|
|
114
146
|
// Tool helpers
|
|
115
147
|
// ---------------------------------------------------------------------------
|
|
116
148
|
/** Determine which tools stay active after blocking read tools.
|
|
@@ -182,6 +214,8 @@ async function default_1(pi) {
|
|
|
182
214
|
depsChecked = true;
|
|
183
215
|
if (!depsReady)
|
|
184
216
|
return;
|
|
217
|
+
// First install: pick reader model from available models
|
|
218
|
+
await pickReaderModel(config, ctx);
|
|
185
219
|
}
|
|
186
220
|
// --- Tool blocking ---
|
|
187
221
|
if (config.enabled) {
|
|
@@ -229,9 +263,13 @@ async function default_1(pi) {
|
|
|
229
263
|
// 4. /read-delegator command
|
|
230
264
|
// -----------------------------------------------------------------------
|
|
231
265
|
pi.registerCommand("read-delegator", {
|
|
232
|
-
description: "Manage read delegation (on|off|status)",
|
|
233
|
-
handler: (args, ctx) => {
|
|
234
|
-
const
|
|
266
|
+
description: "Manage read delegation (on|off|status|model)",
|
|
267
|
+
handler: async (args, ctx) => {
|
|
268
|
+
const raw = args?.trim() ?? "";
|
|
269
|
+
const spaceIdx = raw.indexOf(" ");
|
|
270
|
+
const sub = spaceIdx >= 0
|
|
271
|
+
? raw.slice(0, spaceIdx).toLowerCase()
|
|
272
|
+
: raw.toLowerCase() || "status";
|
|
235
273
|
switch (sub) {
|
|
236
274
|
case "on":
|
|
237
275
|
case "enable": {
|
|
@@ -255,12 +293,31 @@ async function default_1(pi) {
|
|
|
255
293
|
ctx.ui.setStatus("read-delegator", (0, ui_1.msg)("status_off"));
|
|
256
294
|
return;
|
|
257
295
|
}
|
|
296
|
+
case "model": {
|
|
297
|
+
const modelArg = spaceIdx >= 0 ? raw.slice(spaceIdx + 1).trim() : "";
|
|
298
|
+
if (!modelArg) {
|
|
299
|
+
// Interactive picker via available models
|
|
300
|
+
const picked = await pickReaderModel(config, ctx);
|
|
301
|
+
if (picked === undefined) {
|
|
302
|
+
ctx.ui.notify("Current reader model: " +
|
|
303
|
+
config.reader_model +
|
|
304
|
+
"\n\nChange: /read-delegator model <provider/model>", "info");
|
|
305
|
+
}
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
config.reader_model = modelArg;
|
|
309
|
+
(0, config_1.saveConfig)(config, { silent: true });
|
|
310
|
+
syncReaderTemplate(modelArg);
|
|
311
|
+
ctx.ui.notify("✅ Reader model set to: " + modelArg, "info");
|
|
312
|
+
return;
|
|
313
|
+
}
|
|
258
314
|
case "status":
|
|
259
315
|
default: {
|
|
260
316
|
const lines = [
|
|
261
317
|
"Read delegation: " +
|
|
262
318
|
(config.enabled ? "🟢 enabled" : "🔴 disabled"),
|
|
263
319
|
"Reader subagent: " + config.reader_subagent_name,
|
|
320
|
+
"Reader model: " + config.reader_model,
|
|
264
321
|
"Dependencies: " + (depsReady ? "✅ ready" : "❌ missing"),
|
|
265
322
|
"Blocked tools: " + config.blocked_tools.join(", "),
|
|
266
323
|
];
|
|
@@ -270,9 +327,7 @@ async function default_1(pi) {
|
|
|
270
327
|
}
|
|
271
328
|
},
|
|
272
329
|
});
|
|
273
|
-
//
|
|
274
|
-
|
|
275
|
-
// -----------------------------------------------------------------------
|
|
276
|
-
await ensureReaderTemplate();
|
|
330
|
+
// 5. Background: sync reader.md template from config
|
|
331
|
+
syncReaderTemplate(config.reader_model);
|
|
277
332
|
}
|
|
278
333
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED
package/reader-manager.js
CHANGED
|
@@ -91,9 +91,43 @@ function piSubagentsDir() {
|
|
|
91
91
|
function piSubagentsPackageJson() {
|
|
92
92
|
return path.join(piSubagentsDir(), "package.json");
|
|
93
93
|
}
|
|
94
|
+
function piSettingsPath() {
|
|
95
|
+
return path.join(os.homedir(), ".pi", "agent", "settings.json");
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Register pi-subagents in Pi's package list (settings.json).
|
|
99
|
+
*
|
|
100
|
+
* npm install puts the package on disk, but Pi's `pi list` command reads
|
|
101
|
+
* from settings.json's `packages` array. Without this step, the user
|
|
102
|
+
* won't see pi-subagents in their package list.
|
|
103
|
+
*/
|
|
104
|
+
function registerPiSubagentsPackage() {
|
|
105
|
+
try {
|
|
106
|
+
const settingsPath = piSettingsPath();
|
|
107
|
+
let settings = {};
|
|
108
|
+
if (fs.existsSync(settingsPath)) {
|
|
109
|
+
const raw = fs.readFileSync(settingsPath, "utf-8");
|
|
110
|
+
settings = JSON.parse(raw);
|
|
111
|
+
}
|
|
112
|
+
const packages = Array.isArray(settings.packages)
|
|
113
|
+
? [...settings.packages]
|
|
114
|
+
: [];
|
|
115
|
+
const pkgName = "npm:pi-subagents";
|
|
116
|
+
if (!packages.includes(pkgName)) {
|
|
117
|
+
packages.push(pkgName);
|
|
118
|
+
settings.packages = packages;
|
|
119
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n", "utf-8");
|
|
120
|
+
(0, ui_1.rawLog)("✅ Registered pi-subagents in settings.json package list.");
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
catch (err) {
|
|
124
|
+
(0, ui_1.rawWarn)("⚠️ Failed to register pi-subagents in settings.json: " + String(err));
|
|
125
|
+
}
|
|
126
|
+
}
|
|
94
127
|
async function checkDependencies(prompt, onProgress) {
|
|
95
128
|
// Already installed — nothing to do
|
|
96
129
|
if (fs.existsSync(piSubagentsPackageJson())) {
|
|
130
|
+
registerPiSubagentsPackage();
|
|
97
131
|
return true;
|
|
98
132
|
}
|
|
99
133
|
(0, ui_1.rawWarn)("⚠️ pi-subagents is not installed.");
|
|
@@ -114,6 +148,7 @@ async function checkDependencies(prompt, onProgress) {
|
|
|
114
148
|
encoding: "utf-8",
|
|
115
149
|
});
|
|
116
150
|
(0, ui_1.rawLog)("✅ pi-subagents installed via npm.");
|
|
151
|
+
registerPiSubagentsPackage();
|
|
117
152
|
onProgress?.("done");
|
|
118
153
|
}
|
|
119
154
|
catch (firstErr) {
|
package/tool-blocker.js
CHANGED
|
@@ -16,6 +16,7 @@ exports.tempAllowOnce = tempAllowOnce;
|
|
|
16
16
|
exports.isBlocked = isBlocked;
|
|
17
17
|
exports.getBlockedTools = getBlockedTools;
|
|
18
18
|
exports.reset = reset;
|
|
19
|
+
const ui_1 = require("./ui");
|
|
19
20
|
// ---------------------------------------------------------------------------
|
|
20
21
|
// Tool blocker state
|
|
21
22
|
// ---------------------------------------------------------------------------
|
|
@@ -40,10 +41,10 @@ function blockTools(agent, tools) {
|
|
|
40
41
|
removedTools.set(toolName, definition);
|
|
41
42
|
try {
|
|
42
43
|
agent.removeTool(toolName);
|
|
43
|
-
|
|
44
|
+
(0, ui_1.rawLog)(`Blocked tool: ${toolName}`);
|
|
44
45
|
}
|
|
45
46
|
catch (err) {
|
|
46
|
-
|
|
47
|
+
(0, ui_1.rawError)(`Failed to block tool "${toolName}": ${err}`);
|
|
47
48
|
}
|
|
48
49
|
}
|
|
49
50
|
}
|
|
@@ -56,10 +57,10 @@ function restoreTools(agent) {
|
|
|
56
57
|
for (const [toolName, definition] of removedTools.entries()) {
|
|
57
58
|
try {
|
|
58
59
|
agent.addTool(definition);
|
|
59
|
-
|
|
60
|
+
(0, ui_1.rawLog)(`Restored tool: ${toolName}`);
|
|
60
61
|
}
|
|
61
62
|
catch (err) {
|
|
62
|
-
|
|
63
|
+
(0, ui_1.rawError)(`Failed to restore tool "${toolName}": ${err}`);
|
|
63
64
|
}
|
|
64
65
|
}
|
|
65
66
|
removedTools.clear();
|
|
@@ -92,7 +93,7 @@ async function tempAllowOnce(agent, blockedTools, callback) {
|
|
|
92
93
|
restoredNow.push(toolName);
|
|
93
94
|
}
|
|
94
95
|
catch (err) {
|
|
95
|
-
|
|
96
|
+
(0, ui_1.rawError)(`Failed to temporarily restore "${toolName}": ${err}`);
|
|
96
97
|
}
|
|
97
98
|
}
|
|
98
99
|
}
|
|
@@ -113,7 +114,7 @@ async function tempAllowOnce(agent, blockedTools, callback) {
|
|
|
113
114
|
agent.removeTool(toolName);
|
|
114
115
|
}
|
|
115
116
|
catch (err) {
|
|
116
|
-
|
|
117
|
+
(0, ui_1.rawError)(`Failed to re-block "${toolName}": ${err}`);
|
|
117
118
|
}
|
|
118
119
|
}
|
|
119
120
|
}
|