@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 CHANGED
@@ -11,6 +11,7 @@ export interface ReadDelegatorConfig {
11
11
  blocked_tools: string[];
12
12
  allowed_bash_write_commands: string[];
13
13
  orchestrator_prompt: string;
14
+ reader_model: string;
14
15
  language: string;
15
16
  }
16
17
  /**
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
- console.warn(`[pi-read-delegator] Corrupted config file at ${filePath}. Overwriting with defaults. Error: ${err}`);
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
- console.log(`[pi-read-delegator] Config saved to ${filePath}`);
131
+ (0, ui_1.rawLog)(`Config saved to ${filePath}`);
130
132
  }
131
133
  }
132
134
  catch (err) {
133
- console.error(`[pi-read-delegator] Failed to save config: ${err}`);
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
- async function ensureReaderTemplate() {
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
- await fs.promises.writeFile(rp, content, "utf8");
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 creation is best-effort
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 sub = args?.trim().toLowerCase() ?? "status";
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
- // 5. Background: ensure reader.md template
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stfade/pi-read-delegator",
3
- "version": "1.0.11",
3
+ "version": "1.0.12",
4
4
  "description": "Pi extension that delegates all read operations to a Reader subagent, blocking read tools from the main model",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
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
- console.log(`[pi-read-delegator] Blocked tool: ${toolName}`);
44
+ (0, ui_1.rawLog)(`Blocked tool: ${toolName}`);
44
45
  }
45
46
  catch (err) {
46
- console.error(`[pi-read-delegator] Failed to block tool "${toolName}": ${err}`);
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
- console.log(`[pi-read-delegator] Restored tool: ${toolName}`);
60
+ (0, ui_1.rawLog)(`Restored tool: ${toolName}`);
60
61
  }
61
62
  catch (err) {
62
- console.error(`[pi-read-delegator] Failed to restore tool "${toolName}": ${err}`);
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
- console.error(`[pi-read-delegator] Failed to temporarily restore "${toolName}": ${err}`);
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
- console.error(`[pi-read-delegator] Failed to re-block "${toolName}": ${err}`);
117
+ (0, ui_1.rawError)(`Failed to re-block "${toolName}": ${err}`);
117
118
  }
118
119
  }
119
120
  }