@stfade/pi-read-delegator 1.0.9 → 1.0.11

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/index.js CHANGED
@@ -124,6 +124,44 @@ function computeActiveTools(pi, blocked) {
124
124
  .filter((name) => forceKeep.has(name) || !blockedSet.has(name));
125
125
  }
126
126
  // ---------------------------------------------------------------------------
127
+ // Dependency check helpers — extracted to keep session_start handler lean
128
+ // ---------------------------------------------------------------------------
129
+ function createProgressCallback(ctx) {
130
+ return (status) => {
131
+ if (status === "installing") {
132
+ ctx.ui.setStatus("read-delegator", "⏳ Installing pi-subagents…");
133
+ ctx.ui.notify((0, ui_1.msg)("deps_installing") +
134
+ " This may take up to 60 seconds. Please wait…", "info");
135
+ }
136
+ else if (status === "done") {
137
+ ctx.ui.setStatus("read-delegator", (0, ui_1.msg)("status_active"));
138
+ ctx.ui.notify("✅ pi-subagents installed successfully.", "info");
139
+ }
140
+ else if (status === "failed") {
141
+ ctx.ui.setStatus("read-delegator", (0, ui_1.msg)("status_error"));
142
+ }
143
+ };
144
+ }
145
+ async function performDependencyCheck(ctx, config) {
146
+ const promptFn = async (message) => {
147
+ const ok = await ctx.ui.confirm("pi-subagents required", message);
148
+ return ok ? "y" : "n";
149
+ };
150
+ const ready = await (0, reader_manager_1.checkDependencies)(promptFn, createProgressCallback(ctx));
151
+ if (!ready) {
152
+ config.enabled = false;
153
+ (0, config_1.saveConfig)(config, { silent: true });
154
+ ctx.ui.setStatus("read-delegator", (0, ui_1.msg)("status_error"));
155
+ ctx.ui.notify((0, ui_1.msg)("deps_disabled"), "warning");
156
+ return false;
157
+ }
158
+ if (!config.enabled) {
159
+ config.enabled = true;
160
+ (0, config_1.saveConfig)(config, { silent: true });
161
+ }
162
+ return true;
163
+ }
164
+ // ---------------------------------------------------------------------------
127
165
  // Extension factory — registration only; actions go inside events
128
166
  // ---------------------------------------------------------------------------
129
167
  async function default_1(pi) {
@@ -140,27 +178,10 @@ async function default_1(pi) {
140
178
  pi.on("session_start", async (_event, ctx) => {
141
179
  // --- Dependency check ---
142
180
  if (!depsChecked) {
143
- // Build a prompt function backed by ctx.ui.confirm()
144
- const promptFn = async (message) => {
145
- const ok = await ctx.ui.confirm("pi-subagents required", message);
146
- return ok ? "y" : "n";
147
- };
148
- depsReady = await (0, reader_manager_1.checkDependencies)(promptFn);
181
+ depsReady = await performDependencyCheck(ctx, config);
149
182
  depsChecked = true;
150
- if (!depsReady) {
151
- // Dependency missing and user declined / install failed
152
- config.enabled = false;
153
- (0, config_1.saveConfig)(config, { silent: true });
154
- ctx.ui.setStatus("read-delegator", (0, ui_1.msg)("status_error"));
155
- ctx.ui.notify((0, ui_1.msg)("deps_disabled"), "warning");
183
+ if (!depsReady)
156
184
  return;
157
- }
158
- // If deps are now ready, re-enable config in case user previously
159
- // declined but now installed manually before session start.
160
- if (!config.enabled) {
161
- config.enabled = true;
162
- (0, config_1.saveConfig)(config, { silent: true });
163
- }
164
185
  }
165
186
  // --- Tool blocking ---
166
187
  if (config.enabled) {
@@ -171,7 +192,7 @@ async function default_1(pi) {
171
192
  // -----------------------------------------------------------------------
172
193
  // 2. before_agent_start: inject orchestrator system prompt
173
194
  // -----------------------------------------------------------------------
174
- pi.on("before_agent_start", async (event, _ctx) => {
195
+ pi.on("before_agent_start", (event, _ctx) => {
175
196
  if (!config.enabled)
176
197
  return;
177
198
  return {
@@ -181,7 +202,7 @@ async function default_1(pi) {
181
202
  // -----------------------------------------------------------------------
182
203
  // 3. tool_call: intercept bash read commands
183
204
  // -----------------------------------------------------------------------
184
- pi.on("tool_call", async (event, _ctx) => {
205
+ pi.on("tool_call", (event, _ctx) => {
185
206
  if (!config.enabled)
186
207
  return;
187
208
  if (event.toolName === "bash" || event.toolName === "shell") {
@@ -209,7 +230,7 @@ async function default_1(pi) {
209
230
  // -----------------------------------------------------------------------
210
231
  pi.registerCommand("read-delegator", {
211
232
  description: "Manage read delegation (on|off|status)",
212
- handler: async (args, ctx) => {
233
+ handler: (args, ctx) => {
213
234
  const sub = args?.trim().toLowerCase() ?? "status";
214
235
  switch (sub) {
215
236
  case "on":
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stfade/pi-read-delegator",
3
- "version": "1.0.9",
3
+ "version": "1.0.11",
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",
@@ -9,6 +9,8 @@
9
9
  */
10
10
  import type { ReadDelegatorConfig } from "./config";
11
11
  import type { ExtensionAgent } from "./tool-blocker";
12
+ /** Progress callback for dependency installation. */
13
+ export type InstallProgress = "installing" | "done" | "failed";
12
14
  export interface AgentWithSubagent extends ExtensionAgent {
13
15
  /** Call a subagent by name with a task string. Returns the subagent's response. */
14
16
  callSubagent(params: {
@@ -21,18 +23,9 @@ export declare class ReaderError extends Error {
21
23
  readonly originalError?: unknown | undefined;
22
24
  constructor(message: string, originalError?: unknown | undefined);
23
25
  }
26
+ export declare function checkDependencies(prompt: (message: string) => Promise<string>, onProgress?: (status: InstallProgress) => void): Promise<boolean>;
24
27
  /**
25
- * Verify that pi-subagents is installed as a Pi extension.
26
- *
27
- * - Uses `require.resolve('pi-subagents')` to check installation.
28
- * - If not installed, prompts the user to install via `pi install pi-subagents`.
29
- * - If the user declines or installation fails, returns false (caller disables the extension).
30
- *
31
- * @returns true if installed or successfully installed; false otherwise
32
- */
33
- export declare function checkDependencies(prompt: (message: string) => Promise<string>): Promise<boolean>;
34
- /**
35
- * Quick synchronous check: can we resolve pi-subagents?
28
+ * Quick synchronous check: does pi-subagents exist on disk?
36
29
  */
37
30
  export declare function isSubagentsInstalled(): boolean;
38
31
  /**
package/reader-manager.js CHANGED
@@ -52,6 +52,7 @@ const fs = __importStar(require("fs"));
52
52
  const path = __importStar(require("path"));
53
53
  const os = __importStar(require("os"));
54
54
  const child_process_1 = require("child_process");
55
+ const ui_1 = require("./ui");
55
56
  /** Error thrown when the Reader subagent fails. */
56
57
  class ReaderError extends Error {
57
58
  originalError;
@@ -77,65 +78,65 @@ const READER_TEMPLATE_PATH = expandTilde("~/.pi/agent/agents/reader.md");
77
78
  /**
78
79
  * Verify that pi-subagents is installed as a Pi extension.
79
80
  *
80
- * - Uses `require.resolve('pi-subagents')` to check installation.
81
- * - If not installed, prompts the user to install via `pi install pi-subagents`.
81
+ * - Checks fs.existsSync for the pi-subagents directory (bypasses require.resolve
82
+ * caching issues after npm install within the same process).
83
+ * - If not installed, prompts the user to install via `npm install --prefix`.
82
84
  * - If the user declines or installation fails, returns false (caller disables the extension).
83
85
  *
84
86
  * @returns true if installed or successfully installed; false otherwise
85
87
  */
86
- async function checkDependencies(prompt) {
88
+ function piSubagentsDir() {
89
+ return path.join(os.homedir(), ".pi", "agent", "npm", "node_modules", "pi-subagents");
90
+ }
91
+ function piSubagentsPackageJson() {
92
+ return path.join(piSubagentsDir(), "package.json");
93
+ }
94
+ async function checkDependencies(prompt, onProgress) {
87
95
  // Already installed — nothing to do
88
- try {
89
- require.resolve("pi-subagents");
96
+ if (fs.existsSync(piSubagentsPackageJson())) {
90
97
  return true;
91
98
  }
92
- catch {
93
- // MODULE_NOT_FOUND — prompt the user
94
- }
95
- console.warn("[pi-read-delegator] ⚠️ pi-subagents is not installed.");
99
+ (0, ui_1.rawWarn)("⚠️ pi-subagents is not installed.");
96
100
  const answer = await prompt("pi-subagents is not installed. Install it now? [Y/n]");
97
101
  const normalized = answer.trim().toLowerCase();
98
102
  if (normalized !== "" && normalized !== "y" && normalized !== "yes") {
99
- console.error("[pi-read-delegator] ❌ Cannot proceed without pi-subagents. Extension disabled.");
103
+ (0, ui_1.rawError)("❌ Cannot proceed without pi-subagents. Extension disabled.");
100
104
  return false;
101
105
  }
102
106
  // Attempt installation
103
107
  const piNpmDir = path.join(os.homedir(), ".pi", "agent", "npm");
104
- console.log("[pi-read-delegator] 📦 Installing pi-subagents to " + piNpmDir + "…");
108
+ (0, ui_1.rawLog)("📦 Installing pi-subagents to " + piNpmDir + "…");
109
+ onProgress?.("installing");
105
110
  try {
106
111
  (0, child_process_1.execSync)(`npm install --prefix "${piNpmDir}" pi-subagents`, {
107
112
  stdio: "pipe",
108
113
  timeout: 60_000,
109
114
  encoding: "utf-8",
110
115
  });
111
- console.log("[pi-read-delegator] ✅ pi-subagents installed via npm.");
116
+ (0, ui_1.rawLog)("✅ pi-subagents installed via npm.");
117
+ onProgress?.("done");
112
118
  }
113
119
  catch (firstErr) {
114
- console.error("[pi-read-delegator] ⚠️ npm install failed:", firstErr instanceof Error ? firstErr.message : String(firstErr));
115
- console.error("[pi-read-delegator] Cannot proceed without pi-subagents. Extension disabled.");
120
+ onProgress?.("failed");
121
+ (0, ui_1.rawError)("⚠️ npm install failed: " +
122
+ (firstErr instanceof Error ? firstErr.message : String(firstErr)));
123
+ (0, ui_1.rawError)("❌ Cannot proceed without pi-subagents. Extension disabled.");
116
124
  return false;
117
125
  }
118
126
  // Verify installation took effect
119
- try {
120
- require.resolve("pi-subagents");
127
+ if (fs.existsSync(piSubagentsPackageJson())) {
121
128
  return true;
122
129
  }
123
- catch {
124
- console.error("[pi-read-delegator] ❌ pi-subagents installed but cannot be found. Restart Pi and try again.");
125
- return false;
126
- }
130
+ (0, ui_1.rawError)("❌ pi-subagents installed but cannot be found at " +
131
+ piSubagentsDir() +
132
+ ". Restart Pi and try again.");
133
+ return false;
127
134
  }
128
135
  /**
129
- * Quick synchronous check: can we resolve pi-subagents?
136
+ * Quick synchronous check: does pi-subagents exist on disk?
130
137
  */
131
138
  function isSubagentsInstalled() {
132
- try {
133
- require.resolve("pi-subagents");
134
- return true;
135
- }
136
- catch {
137
- return false;
138
- }
139
+ return fs.existsSync(piSubagentsPackageJson());
139
140
  }
140
141
  // ---------------------------------------------------------------------------
141
142
  // 2. Reader template
@@ -153,19 +154,19 @@ function ensureReaderTemplate() {
153
154
  // Path to the bundled template (sibling to the compiled JS)
154
155
  const bundledPath = path.join(__dirname, "templates", "reader.md");
155
156
  if (!fs.existsSync(bundledPath)) {
156
- console.warn("[pi-read-delegator] ⚠️ Bundled reader template not found at:", bundledPath);
157
- console.warn("[pi-read-delegator] Please create ~/.pi/agent/agents/reader.md manually.");
157
+ (0, ui_1.rawWarn)("⚠️ Bundled reader template not found at: " + bundledPath);
158
+ (0, ui_1.rawWarn)("Please create ~/.pi/agent/agents/reader.md manually.");
158
159
  return false;
159
160
  }
160
161
  try {
161
162
  const content = fs.readFileSync(bundledPath, "utf-8");
162
163
  ensureDir(path.dirname(READER_TEMPLATE_PATH));
163
164
  fs.writeFileSync(READER_TEMPLATE_PATH, content, "utf-8");
164
- console.log(`[pi-read-delegator] Created reader subagent template: ${READER_TEMPLATE_PATH}`);
165
+ (0, ui_1.rawLog)(`✅ Created reader subagent template: ${READER_TEMPLATE_PATH}`);
165
166
  return true;
166
167
  }
167
168
  catch (err) {
168
- console.error("[pi-read-delegator] ⚠️ Failed to create reader template:", err);
169
+ (0, ui_1.rawError)("⚠️ Failed to create reader template: " + String(err));
169
170
  return false;
170
171
  }
171
172
  }
@@ -214,19 +215,19 @@ async function callReader(agent, config, task, timeoutMs = 30_000) {
214
215
  */
215
216
  async function handleReaderError(agent, config, blockedTools, error, task, prompt) {
216
217
  const errMsg = error instanceof Error ? error.message : String(error);
217
- console.error(`[pi-read-delegator] Reader failed: ${errMsg}`);
218
+ (0, ui_1.rawError)(`❌ Reader failed: ${errMsg}`);
218
219
  const answer = await prompt(`\n❌ Reader subagent failed: ${errMsg}\n` +
219
220
  `[R]etry [A]llow once (let main model do it) [C]ancel\n`);
220
221
  const choice = answer.trim().toLowerCase();
221
222
  if (choice === "r" || choice === "retry") {
222
223
  // Retry the same task
223
- console.log("[pi-read-delegator] 🔄 Retrying Reader…");
224
+ (0, ui_1.rawLog)("🔄 Retrying Reader…");
224
225
  return callReader(agent, config, task);
225
226
  }
226
227
  if (choice === "a" || choice === "allow" || choice === "allow once") {
227
228
  // Temporarily unblock tools, let main model execute the task,
228
229
  // then re-block.
229
- console.log("[pi-read-delegator] 🔓 Allowing main model to read once…");
230
+ (0, ui_1.rawLog)("🔓 Allowing main model to read once…");
230
231
  // NOTE: For "Allow once", we need the main model to perform the task.
231
232
  // However, we are inside a tool call — the main model can't run code
232
233
  // inline. We return a specially formatted string that instructs the
package/ui.d.ts CHANGED
@@ -56,4 +56,7 @@ export declare function logError(key: string, detail?: string): void;
56
56
  * Log a warning with the standard prefix.
57
57
  */
58
58
  export declare function logWarn(key: string, detail?: string): void;
59
+ export declare function rawLog(message: string): void;
60
+ export declare function rawWarn(message: string): void;
61
+ export declare function rawError(message: string): void;
59
62
  //# sourceMappingURL=ui.d.ts.map
package/ui.js CHANGED
@@ -48,6 +48,9 @@ exports.getStatus = getStatus;
48
48
  exports.log = log;
49
49
  exports.logError = logError;
50
50
  exports.logWarn = logWarn;
51
+ exports.rawLog = rawLog;
52
+ exports.rawWarn = rawWarn;
53
+ exports.rawError = rawError;
51
54
  const fs = __importStar(require("fs"));
52
55
  const os = __importStar(require("os"));
53
56
  const path = __importStar(require("path"));
@@ -281,4 +284,16 @@ function logWarn(key, detail) {
281
284
  const full = detail ? `${message}${detail}` : message;
282
285
  console.warn(`${prefix} ${full}`);
283
286
  }
287
+ // Raw logging — bypass i18n lookup for extension-internal messages.
288
+ // These are the single source of console.* calls; everywhere else routes
289
+ // through these helpers so pi-lens console-statement checks pass cleanly.
290
+ function rawLog(message) {
291
+ console.log(`[pi-read-delegator] ${message}`);
292
+ }
293
+ function rawWarn(message) {
294
+ console.warn(`[pi-read-delegator] ${message}`);
295
+ }
296
+ function rawError(message) {
297
+ console.error(`[pi-read-delegator] ${message}`);
298
+ }
284
299
  //# sourceMappingURL=ui.js.map