@stfade/pi-read-delegator 1.0.6 → 1.0.7

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.d.ts CHANGED
@@ -1,3 +1,15 @@
1
+ /**
2
+ * index.ts — pi-read-delegator entry point
3
+ *
4
+ * Blocks read tools from the orchestrator and tells it to delegate every
5
+ * file-read / search task to the 'reader' subagent.
6
+ *
7
+ * Architecture:
8
+ * - Factory body: registration only (pi.on, pi.registerCommand, ensureReaderTemplate)
9
+ * - session_start: dependency check → tool blocking → status bar
10
+ * - before_agent_start: inject orchestrator system prompt
11
+ * - tool_call: intercept bash read commands → redirect to reader
12
+ */
1
13
  import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
2
14
  export default function (pi: ExtensionAPI): Promise<void>;
3
15
  //# sourceMappingURL=index.d.ts.map
package/index.js CHANGED
@@ -1,4 +1,16 @@
1
1
  "use strict";
2
+ /**
3
+ * index.ts — pi-read-delegator entry point
4
+ *
5
+ * Blocks read tools from the orchestrator and tells it to delegate every
6
+ * file-read / search task to the 'reader' subagent.
7
+ *
8
+ * Architecture:
9
+ * - Factory body: registration only (pi.on, pi.registerCommand, ensureReaderTemplate)
10
+ * - session_start: dependency check → tool blocking → status bar
11
+ * - before_agent_start: inject orchestrator system prompt
12
+ * - tool_call: intercept bash read commands → redirect to reader
13
+ */
2
14
  var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
15
  if (k2 === undefined) k2 = k;
4
16
  var desc = Object.getOwnPropertyDescriptor(m, k);
@@ -37,19 +49,12 @@ exports.default = default_1;
37
49
  const fs = __importStar(require("node:fs"));
38
50
  const os = __importStar(require("node:os"));
39
51
  const path = __importStar(require("node:path"));
40
- const DEFAULT_CONFIG = {
41
- enabled: true,
42
- reader_subagent_name: "reader",
43
- blocked_tools: ["read", "grep", "find", "ls"],
44
- orchestrator_prompt: [
45
- "You are an orchestrator. You do NOT have direct file-reading tools.",
46
- "For any file reading, searching, or directory listing, use the",
47
- "'subagent' tool with agent='reader'.",
48
- 'Example: subagent(agent: "reader", task: "Find all TS files that import \'lodash\'")',
49
- "Never try to use read, grep, find, or ls yourself. Always delegate.",
50
- ].join("\n"),
51
- language: "auto",
52
- };
52
+ const config_1 = require("./config");
53
+ const reader_manager_1 = require("./reader-manager");
54
+ const ui_1 = require("./ui");
55
+ // ---------------------------------------------------------------------------
56
+ // Bash read commands — these are intercepted and redirected to reader
57
+ // ---------------------------------------------------------------------------
53
58
  const READ_BASH_COMMANDS = new Set([
54
59
  "cat",
55
60
  "grep",
@@ -74,45 +79,12 @@ const READ_BASH_COMMANDS = new Set([
74
79
  "type",
75
80
  "dir",
76
81
  ]);
77
- function configPath() {
78
- return path.join(os.homedir(), ".pi", "agent", "read-delegator.json");
79
- }
82
+ // ---------------------------------------------------------------------------
83
+ // Reader template path
84
+ // ---------------------------------------------------------------------------
80
85
  function readerPath() {
81
86
  return path.join(os.homedir(), ".pi", "agent", "agents", "reader.md");
82
87
  }
83
- function loadConfig() {
84
- const cp = configPath();
85
- try {
86
- if (fs.existsSync(cp)) {
87
- const raw = fs.readFileSync(cp, "utf8");
88
- const parsed = JSON.parse(raw);
89
- return { ...DEFAULT_CONFIG, ...parsed };
90
- }
91
- }
92
- catch {
93
- // corrupt file --- fall back to defaults
94
- }
95
- try {
96
- const dir = path.dirname(cp);
97
- fs.mkdirSync(dir, { recursive: true });
98
- fs.writeFileSync(cp, JSON.stringify(DEFAULT_CONFIG, null, 2), "utf8");
99
- }
100
- catch {
101
- // read-only home directory --- ignore
102
- }
103
- return { ...DEFAULT_CONFIG };
104
- }
105
- function saveConfig(config) {
106
- const cp = configPath();
107
- try {
108
- const dir = path.dirname(cp);
109
- fs.mkdirSync(dir, { recursive: true });
110
- fs.writeFileSync(cp, JSON.stringify(config, null, 2), "utf8");
111
- }
112
- catch {
113
- // read-only home directory --- ignore
114
- }
115
- }
116
88
  async function ensureReaderTemplate() {
117
89
  const rp = readerPath();
118
90
  if (fs.existsSync(rp))
@@ -135,31 +107,70 @@ async function ensureReaderTemplate() {
135
107
  await fs.promises.writeFile(rp, content, "utf8");
136
108
  }
137
109
  catch {
138
- // read-only home directory --- template creation is best-effort
110
+ // read-only home directory template creation is best-effort
139
111
  }
140
112
  }
141
- /**
142
- * Determine which tools should stay active after blocking read tools.
143
- *
144
- * We MUST keep the 'subagent' tool (registered by pi-subagents) active;
145
- * otherwise the orchestrator cannot call the reader at all.
146
- */
113
+ // ---------------------------------------------------------------------------
114
+ // Tool helpers
115
+ // ---------------------------------------------------------------------------
116
+ /** Determine which tools stay active after blocking read tools.
117
+ * Always keeps "subagent" — the bridge to the reader. */
147
118
  function computeActiveTools(pi, blocked) {
148
119
  const all = pi.getAllTools();
149
120
  const blockedSet = new Set(blocked);
150
- // Always keep "subagent" --- it is the bridge to the reader.
151
121
  const forceKeep = new Set(["subagent"]);
152
122
  return all
153
123
  .map((t) => t.name)
154
124
  .filter((name) => forceKeep.has(name) || !blockedSet.has(name));
155
125
  }
156
126
  // ---------------------------------------------------------------------------
157
- // Extension entry
127
+ // Extension factory — registration only; actions go inside events
158
128
  // ---------------------------------------------------------------------------
159
129
  async function default_1(pi) {
160
- const config = loadConfig();
161
- // --- ALL actions go inside events. Factory body is registration-only. ---
162
- // Inject orchestrator system prompt
130
+ const config = (0, config_1.loadConfig)();
131
+ // Detect language
132
+ (0, ui_1.getLanguage)(config.language);
133
+ // Quick sync dependency check — interactive prompt is deferred to
134
+ // session_start where we have access to ctx.ui.confirm().
135
+ let depsReady = (0, reader_manager_1.isSubagentsInstalled)();
136
+ let depsChecked = depsReady; // if already ready, no need to check again
137
+ // -----------------------------------------------------------------------
138
+ // 1. session_start: dependency check → tool blocking → status bar
139
+ // -----------------------------------------------------------------------
140
+ pi.on("session_start", async (_event, ctx) => {
141
+ // --- Dependency check ---
142
+ 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);
149
+ 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");
156
+ 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
+ }
165
+ // --- Tool blocking ---
166
+ if (config.enabled) {
167
+ pi.setActiveTools(computeActiveTools(pi, config.blocked_tools));
168
+ }
169
+ ctx.ui.setStatus("read-delegator", config.enabled ? (0, ui_1.msg)("status_active") : (0, ui_1.msg)("status_off"));
170
+ });
171
+ // -----------------------------------------------------------------------
172
+ // 2. before_agent_start: inject orchestrator system prompt
173
+ // -----------------------------------------------------------------------
163
174
  pi.on("before_agent_start", async (event, _ctx) => {
164
175
  if (!config.enabled)
165
176
  return;
@@ -167,19 +178,16 @@ async function default_1(pi) {
167
178
  systemPrompt: event.systemPrompt + "\n\n" + config.orchestrator_prompt,
168
179
  };
169
180
  });
170
- // Block tools and set status bar AFTER runtime is ready
171
- pi.on("session_start", async (_event, ctx) => {
172
- if (config.enabled) {
173
- pi.setActiveTools(computeActiveTools(pi, config.blocked_tools));
174
- }
175
- ctx.ui.setStatus("read-delegator", config.enabled ? "● reader: " + config.reader_subagent_name : undefined);
176
- });
177
- // Intercept bash read commands
181
+ // -----------------------------------------------------------------------
182
+ // 3. tool_call: intercept bash read commands
183
+ // -----------------------------------------------------------------------
178
184
  pi.on("tool_call", async (event, _ctx) => {
179
185
  if (!config.enabled)
180
186
  return;
181
187
  if (event.toolName === "bash" || event.toolName === "shell") {
182
188
  const command = String(event.input?.command ?? "");
189
+ if (!command)
190
+ return;
183
191
  const firstWord = command.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
184
192
  if (READ_BASH_COMMANDS.has(firstWord)) {
185
193
  return {
@@ -196,7 +204,9 @@ async function default_1(pi) {
196
204
  }
197
205
  }
198
206
  });
199
- // Register /read-delegator command
207
+ // -----------------------------------------------------------------------
208
+ // 4. /read-delegator command
209
+ // -----------------------------------------------------------------------
200
210
  pi.registerCommand("read-delegator", {
201
211
  description: "Manage read delegation (on|off|status)",
202
212
  handler: async (args, ctx) => {
@@ -204,20 +214,24 @@ async function default_1(pi) {
204
214
  switch (sub) {
205
215
  case "on":
206
216
  case "enable": {
217
+ if (!depsReady) {
218
+ ctx.ui.notify("pi-subagents not installed. Install it first to enable read delegation.", "warning");
219
+ return;
220
+ }
207
221
  config.enabled = true;
208
- saveConfig(config);
222
+ (0, config_1.saveConfig)(config, { silent: true });
209
223
  pi.setActiveTools(computeActiveTools(pi, config.blocked_tools));
210
- ctx.ui.notify("🟢 Read delegation enabled", "info");
211
- ctx.ui.setStatus("read-delegator", "● reader: " + config.reader_subagent_name);
224
+ ctx.ui.notify((0, ui_1.msg)("enabled"), "info");
225
+ ctx.ui.setStatus("read-delegator", (0, ui_1.msg)("status_active"));
212
226
  return;
213
227
  }
214
228
  case "off":
215
229
  case "disable": {
216
230
  config.enabled = false;
217
- saveConfig(config);
231
+ (0, config_1.saveConfig)(config, { silent: true });
218
232
  pi.setActiveTools(pi.getAllTools().map((t) => t.name));
219
- ctx.ui.notify("🔴 Read delegation disabled", "info");
220
- ctx.ui.setStatus("read-delegator", undefined);
233
+ ctx.ui.notify((0, ui_1.msg)("disabled"), "info");
234
+ ctx.ui.setStatus("read-delegator", (0, ui_1.msg)("status_off"));
221
235
  return;
222
236
  }
223
237
  case "status":
@@ -225,8 +239,9 @@ async function default_1(pi) {
225
239
  const lines = [
226
240
  "Read delegation: " +
227
241
  (config.enabled ? "🟢 enabled" : "🔴 disabled"),
228
- "Blocked tools: " + config.blocked_tools.join(", "),
229
242
  "Reader subagent: " + config.reader_subagent_name,
243
+ "Dependencies: " + (depsReady ? "✅ ready" : "❌ missing"),
244
+ "Blocked tools: " + config.blocked_tools.join(", "),
230
245
  ];
231
246
  ctx.ui.notify(lines.join("\n"), "info");
232
247
  return;
@@ -234,7 +249,9 @@ async function default_1(pi) {
234
249
  }
235
250
  },
236
251
  });
237
- // Ensure reader.md template (independent I/O — no Pi API call)
252
+ // -----------------------------------------------------------------------
253
+ // 5. Background: ensure reader.md template
254
+ // -----------------------------------------------------------------------
238
255
  await ensureReaderTemplate();
239
256
  }
240
257
  //# 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.6",
3
+ "version": "1.0.7",
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",
@@ -24,14 +24,17 @@ export declare class ReaderError extends Error {
24
24
  /**
25
25
  * Verify that pi-subagents is installed as a Pi extension.
26
26
  *
27
- * Strategy: check whether the `pi-subagents` npm package is findable.
28
- * If not, prompt the user to install it. If they agree, install via
29
- * `pi install pi-subagents` (fallback: `npm install -g pi-subagents`).
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
30
  *
31
- * @returns true if installed or successfully installed
32
- * @throws if the user declines or installation fails
31
+ * @returns true if installed or successfully installed; false otherwise
33
32
  */
34
33
  export declare function checkDependencies(prompt: (message: string) => Promise<string>): Promise<boolean>;
34
+ /**
35
+ * Quick synchronous check: can we resolve pi-subagents?
36
+ */
37
+ export declare function isSubagentsInstalled(): boolean;
35
38
  /**
36
39
  * Ensure the reader.md subagent template exists.
37
40
  * If not, copy the bundled template from `templates/reader.md`.
package/reader-manager.js CHANGED
@@ -44,6 +44,7 @@ var __importStar = (this && this.__importStar) || (function () {
44
44
  Object.defineProperty(exports, "__esModule", { value: true });
45
45
  exports.ReaderError = void 0;
46
46
  exports.checkDependencies = checkDependencies;
47
+ exports.isSubagentsInstalled = isSubagentsInstalled;
47
48
  exports.ensureReaderTemplate = ensureReaderTemplate;
48
49
  exports.callReader = callReader;
49
50
  exports.handleReaderError = handleReaderError;
@@ -76,28 +77,31 @@ const READER_TEMPLATE_PATH = expandTilde("~/.pi/agent/agents/reader.md");
76
77
  /**
77
78
  * Verify that pi-subagents is installed as a Pi extension.
78
79
  *
79
- * Strategy: check whether the `pi-subagents` npm package is findable.
80
- * If not, prompt the user to install it. If they agree, install via
81
- * `pi install pi-subagents` (fallback: `npm install -g pi-subagents`).
80
+ * - Uses `require.resolve('pi-subagents')` to check installation.
81
+ * - If not installed, prompts the user to install via `pi install pi-subagents`.
82
+ * - If the user declines or installation fails, returns false (caller disables the extension).
82
83
  *
83
- * @returns true if installed or successfully installed
84
- * @throws if the user declines or installation fails
84
+ * @returns true if installed or successfully installed; false otherwise
85
85
  */
86
86
  async function checkDependencies(prompt) {
87
- // Try to resolve pi-subagents to verify it's installed
88
- if (isSubagentsInstalled()) {
87
+ // Already installed nothing to do
88
+ try {
89
+ require.resolve("pi-subagents");
89
90
  return true;
90
91
  }
92
+ catch {
93
+ // MODULE_NOT_FOUND — prompt the user
94
+ }
91
95
  console.warn("[pi-read-delegator] ⚠️ pi-subagents is not installed.");
92
- const answer = await prompt("⚠️ pi-subagents is not installed. Install it now? [Y/n]");
96
+ const answer = await prompt("pi-subagents is not installed. Install it now? [Y/n]");
93
97
  const normalized = answer.trim().toLowerCase();
94
98
  if (normalized !== "" && normalized !== "y" && normalized !== "yes") {
95
- throw new Error("pi-subagents is required. Please install it manually: pi install pi-subagents");
99
+ console.error("[pi-read-delegator] Cannot proceed without pi-subagents. Extension disabled.");
100
+ return false;
96
101
  }
97
102
  // Attempt installation
98
103
  console.log("[pi-read-delegator] 📦 Installing pi-subagents…");
99
104
  try {
100
- // Try `pi install pi-subagents` first (the Pi package manager)
101
105
  (0, child_process_1.execSync)("pi install pi-subagents", {
102
106
  stdio: "pipe",
103
107
  timeout: 60_000,
@@ -105,7 +109,7 @@ async function checkDependencies(prompt) {
105
109
  });
106
110
  console.log("[pi-read-delegator] ✅ pi-subagents installed via pi.");
107
111
  }
108
- catch {
112
+ catch (firstErr) {
109
113
  // Fallback to global npm install
110
114
  try {
111
115
  (0, child_process_1.execSync)("npm install -g pi-subagents", {
@@ -115,24 +119,28 @@ async function checkDependencies(prompt) {
115
119
  });
116
120
  console.log("[pi-read-delegator] ✅ pi-subagents installed via npm.");
117
121
  }
118
- catch (err) {
119
- throw new Error("❌ Installation failed. Please install manually: npm install -g pi-subagents");
122
+ catch (secondErr) {
123
+ console.error("[pi-read-delegator] Cannot proceed without pi-subagents. Extension disabled.");
124
+ return false;
120
125
  }
121
126
  }
122
127
  // Verify installation took effect
123
- if (!isSubagentsInstalled()) {
124
- throw new Error("pi-subagents installed but cannot be found. Restart Pi and try again.");
128
+ try {
129
+ require.resolve("pi-subagents");
130
+ return true;
131
+ }
132
+ catch {
133
+ console.error("[pi-read-delegator] ❌ pi-subagents installed but cannot be found. Restart Pi and try again.");
134
+ return false;
125
135
  }
126
- return true;
127
136
  }
128
137
  /**
129
- * Simple check: can we require/import pi-subagents?
138
+ * Quick synchronous check: can we resolve pi-subagents?
130
139
  */
131
140
  function isSubagentsInstalled() {
132
141
  try {
133
- // Dynamic require that works even if TypeScript doesn't know the module
134
- const mod = require("pi-subagents");
135
- return mod !== undefined;
142
+ require.resolve("pi-subagents");
143
+ return true;
136
144
  }
137
145
  catch {
138
146
  return false;
package/ui.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Detects the user's language preference (config → Pi settings → OS → "en")
5
5
  * and provides localized messages. Manages a status bar indicator.
6
6
  */
7
- export type Status = "active" | "idle" | "error";
7
+ export type Status = "active" | "idle" | "error" | "off";
8
8
  /** Minimal Pi agent interface for the status bar API. */
9
9
  export interface AgentWithStatusBar {
10
10
  /** Set text to display in the Pi status bar. */
package/ui.js CHANGED
@@ -84,17 +84,25 @@ const messages = {
84
84
  tr: "❌ Kurulum başarısız. Elle yükleyin.",
85
85
  en: "❌ Installation failed. Install manually.",
86
86
  },
87
+ deps_disabled: {
88
+ tr: "pi-subagents olmadan devam edilemez. Eklenti devre dışı.",
89
+ en: "Cannot proceed without pi-subagents. Extension disabled.",
90
+ },
87
91
  status_active: {
88
- tr: " aktif",
89
- en: " active",
92
+ tr: "🟢 reader",
93
+ en: "🟢 reader",
90
94
  },
91
95
  status_idle: {
92
- tr: "○ boşta",
93
- en: "○ idle",
96
+ tr: "○ reader",
97
+ en: "○ reader",
94
98
  },
95
99
  status_error: {
96
- tr: "⚠ hata",
97
- en: "⚠ error",
100
+ tr: "⚠ reader",
101
+ en: "⚠ reader",
102
+ },
103
+ status_off: {
104
+ tr: "🔴 reader",
105
+ en: "🔴 reader",
98
106
  },
99
107
  blocked: {
100
108
  tr: "🚫 Engellendi: ",
@@ -217,12 +225,15 @@ function updateStatusBar(status) {
217
225
  case "error":
218
226
  text = msg("status_error");
219
227
  break;
228
+ case "off":
229
+ text = msg("status_off");
230
+ break;
220
231
  default:
221
232
  text = msg("status_idle");
222
233
  break;
223
234
  }
224
235
  try {
225
- statusBarAgent.setStatusBarText(`pi-read-delegator: ${text}`);
236
+ statusBarAgent.setStatusBarText(text);
226
237
  }
227
238
  catch {
228
239
  // Graceful fallback — not all Pi versions support status bar