@stfade/pi-read-delegator 1.0.4 → 1.0.5
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 +2 -60
- package/index.js +208 -232
- package/package.json +1 -1
package/index.d.ts
CHANGED
|
@@ -1,61 +1,3 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*
|
|
4
|
-
* Lifecycle:
|
|
5
|
-
* init(agent) → load config, check deps, ensure template, enable/disable
|
|
6
|
-
* enable(agent) → block tools, add system prompt, attach bash filter
|
|
7
|
-
* disable(agent) → restore tools, remove prompt, detach bash filter
|
|
8
|
-
*
|
|
9
|
-
* Commands:
|
|
10
|
-
* /read-delegator on → enable the delegator
|
|
11
|
-
* /read-delegator off → disable the delegator
|
|
12
|
-
* /read-delegator status → show current status
|
|
13
|
-
*/
|
|
14
|
-
import { type AgentWithSubagent } from "./reader-manager";
|
|
15
|
-
/**
|
|
16
|
-
* The Pi agent interface as consumed by pi-read-delegator.
|
|
17
|
-
* Extends the building-block types from sub-modules.
|
|
18
|
-
*/
|
|
19
|
-
export interface PiAgent extends AgentWithSubagent {
|
|
20
|
-
/** Return current tool definitions. */
|
|
21
|
-
getTools(): Array<{
|
|
22
|
-
name: string;
|
|
23
|
-
}>;
|
|
24
|
-
/** Remove a tool by name. */
|
|
25
|
-
removeTool(name: string): void;
|
|
26
|
-
/** Add/re-add a tool definition. */
|
|
27
|
-
addTool(definition: {
|
|
28
|
-
name: string;
|
|
29
|
-
[key: string]: unknown;
|
|
30
|
-
}): void;
|
|
31
|
-
/** Append a persistent system message to the conversation. */
|
|
32
|
-
addSystemMessage(text: string): void;
|
|
33
|
-
/** Remove a previously-added system message by its exact text. */
|
|
34
|
-
removeSystemMessage(text: string): void;
|
|
35
|
-
/** Register a hook that fires BEFORE a tool with the given name is called. */
|
|
36
|
-
onBeforeToolCall(toolName: string, callback: (params: unknown) => Promise<unknown> | unknown): void;
|
|
37
|
-
/** Register a Pi command (like /read-delegator on). */
|
|
38
|
-
registerCommand(name: string, handler: (args: string[]) => Promise<string> | string): void;
|
|
39
|
-
/** Execute a raw shell command directly on the system. */
|
|
40
|
-
executeShellCommand(command: string): Promise<{
|
|
41
|
-
stdout: string;
|
|
42
|
-
stderr: string;
|
|
43
|
-
}>;
|
|
44
|
-
/** Prompt the user for input. */
|
|
45
|
-
promptUser(message: string): Promise<string>;
|
|
46
|
-
/** Display a message to the user. */
|
|
47
|
-
displayMessage(message: string): void;
|
|
48
|
-
/** Set status bar text. */
|
|
49
|
-
setStatusBarText(text: string): void;
|
|
50
|
-
}
|
|
51
|
-
/**
|
|
52
|
-
* Initialize the extension.
|
|
53
|
-
*
|
|
54
|
-
* This is the function Pi calls when loading the extension.
|
|
55
|
-
* It returns a lifecycle object with enable() and disable().
|
|
56
|
-
*/
|
|
57
|
-
export declare function init(agent: PiAgent): {
|
|
58
|
-
enable: () => void;
|
|
59
|
-
disable: () => void;
|
|
60
|
-
};
|
|
1
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
2
|
+
export default function (pi: ExtensionAPI): Promise<void>;
|
|
61
3
|
//# sourceMappingURL=index.d.ts.map
|
package/index.js
CHANGED
|
@@ -1,260 +1,236 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
* enable(agent) → block tools, add system prompt, attach bash filter
|
|
8
|
-
* disable(agent) → restore tools, remove prompt, detach bash filter
|
|
9
|
-
*
|
|
10
|
-
* Commands:
|
|
11
|
-
* /read-delegator on → enable the delegator
|
|
12
|
-
* /read-delegator off → disable the delegator
|
|
13
|
-
* /read-delegator status → show current status
|
|
14
|
-
*/
|
|
15
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
16
|
-
exports.init = init;
|
|
17
|
-
const config_1 = require("./config");
|
|
18
|
-
const tool_blocker_1 = require("./tool-blocker");
|
|
19
|
-
const bash_filter_1 = require("./bash-filter");
|
|
20
|
-
const reader_manager_1 = require("./reader-manager");
|
|
21
|
-
const ui_1 = require("./ui");
|
|
22
|
-
// ---------------------------------------------------------------------------
|
|
23
|
-
// Module state
|
|
24
|
-
// ---------------------------------------------------------------------------
|
|
25
|
-
let enabled = false;
|
|
26
|
-
let config = null;
|
|
27
|
-
let currentSystemMessage = null;
|
|
28
|
-
// ---------------------------------------------------------------------------
|
|
29
|
-
// Lifecycle: init
|
|
30
|
-
// ---------------------------------------------------------------------------
|
|
31
|
-
/**
|
|
32
|
-
* Initialize the extension.
|
|
33
|
-
*
|
|
34
|
-
* This is the function Pi calls when loading the extension.
|
|
35
|
-
* It returns a lifecycle object with enable() and disable().
|
|
36
|
-
*/
|
|
37
|
-
function init(agent) {
|
|
38
|
-
// 1. Load configuration
|
|
39
|
-
config = (0, config_1.loadConfig)();
|
|
40
|
-
// 2. Detect language
|
|
41
|
-
(0, ui_1.getLanguage)(config.language);
|
|
42
|
-
// 3. Initialize status bar
|
|
43
|
-
(0, ui_1.initStatusBar)(agent);
|
|
44
|
-
// 4. Register commands
|
|
45
|
-
registerCommands(agent);
|
|
46
|
-
// 5. Run async init tasks (dependency check, template) in background.
|
|
47
|
-
// We do NOT block init — if deps are missing the user will be prompted.
|
|
48
|
-
initAsync(agent);
|
|
49
|
-
// Build lifecycle interface
|
|
50
|
-
const enable = () => doEnable(agent);
|
|
51
|
-
const disable = () => doDisable(agent);
|
|
52
|
-
// If config says enabled, auto-enable (synchronous part first)
|
|
53
|
-
if (config?.enabled) {
|
|
54
|
-
doEnable(agent);
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
55
7
|
}
|
|
56
|
-
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.default = default_1;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const os = __importStar(require("node:os"));
|
|
39
|
+
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
|
+
};
|
|
53
|
+
const READ_BASH_COMMANDS = new Set([
|
|
54
|
+
"cat",
|
|
55
|
+
"grep",
|
|
56
|
+
"find",
|
|
57
|
+
"ls",
|
|
58
|
+
"head",
|
|
59
|
+
"tail",
|
|
60
|
+
"less",
|
|
61
|
+
"wc",
|
|
62
|
+
"nl",
|
|
63
|
+
"more",
|
|
64
|
+
"bat",
|
|
65
|
+
"rg",
|
|
66
|
+
"fd",
|
|
67
|
+
"awk",
|
|
68
|
+
"du",
|
|
69
|
+
"df",
|
|
70
|
+
"stat",
|
|
71
|
+
"file",
|
|
72
|
+
"which",
|
|
73
|
+
"where",
|
|
74
|
+
"type",
|
|
75
|
+
"dir",
|
|
76
|
+
]);
|
|
77
|
+
function configPath() {
|
|
78
|
+
return path.join(os.homedir(), ".pi", "agent", "read-delegator.json");
|
|
57
79
|
}
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
80
|
+
function readerPath() {
|
|
81
|
+
return path.join(os.homedir(), ".pi", "agent", "agents", "reader.md");
|
|
82
|
+
}
|
|
83
|
+
function loadConfig() {
|
|
84
|
+
const cp = configPath();
|
|
62
85
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
86
|
+
if (fs.existsSync(cp)) {
|
|
87
|
+
const raw = fs.readFileSync(cp, "utf8");
|
|
88
|
+
const parsed = JSON.parse(raw);
|
|
89
|
+
return { ...DEFAULT_CONFIG, ...parsed };
|
|
90
|
+
}
|
|
65
91
|
}
|
|
66
|
-
catch
|
|
67
|
-
|
|
68
|
-
(0, ui_1.logError)("reader_failed", String(err));
|
|
69
|
-
// Disable the extension if dependencies can't be satisfied
|
|
70
|
-
doDisable(agent);
|
|
71
|
-
return;
|
|
92
|
+
catch {
|
|
93
|
+
// corrupt file --- fall back to defaults
|
|
72
94
|
}
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
(
|
|
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
|
|
77
102
|
}
|
|
103
|
+
return { ...DEFAULT_CONFIG };
|
|
78
104
|
}
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
return;
|
|
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");
|
|
86
111
|
}
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
return;
|
|
112
|
+
catch {
|
|
113
|
+
// read-only home directory --- ignore
|
|
90
114
|
}
|
|
91
|
-
// Block read tools
|
|
92
|
-
(0, tool_blocker_1.blockTools)(agent, config.blocked_tools);
|
|
93
|
-
// Add system message
|
|
94
|
-
currentSystemMessage = config.orchestrator_prompt;
|
|
95
|
-
agent.addSystemMessage(config.orchestrator_prompt);
|
|
96
|
-
// Attach bash filter hook
|
|
97
|
-
attachBashFilter(agent);
|
|
98
|
-
// Update status
|
|
99
|
-
enabled = true;
|
|
100
|
-
(0, ui_1.updateStatusBar)("active");
|
|
101
|
-
(0, ui_1.log)("enabled");
|
|
102
|
-
agent.displayMessage((0, ui_1.msg)("enabled"));
|
|
103
115
|
}
|
|
104
|
-
function
|
|
105
|
-
|
|
106
|
-
|
|
116
|
+
async function ensureReaderTemplate() {
|
|
117
|
+
const rp = readerPath();
|
|
118
|
+
if (fs.existsSync(rp))
|
|
107
119
|
return;
|
|
120
|
+
const content = [
|
|
121
|
+
"---",
|
|
122
|
+
"name: reader",
|
|
123
|
+
"description: Token-efficient code reader that returns minimal results.",
|
|
124
|
+
"tools: read, grep, find, ls",
|
|
125
|
+
"model: lmstudio/nvidia/nemotron-3-nano-4b",
|
|
126
|
+
"---",
|
|
127
|
+
"",
|
|
128
|
+
"You are a token-efficient analyst. Execute read/search/list tasks and return",
|
|
129
|
+
"ONLY the essential result. Maximum 10 lines. Use bullet summaries.",
|
|
130
|
+
"Never dump entire files. Focus only on what was asked.",
|
|
131
|
+
].join("\n");
|
|
132
|
+
try {
|
|
133
|
+
const dir = path.dirname(rp);
|
|
134
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
135
|
+
await fs.promises.writeFile(rp, content, "utf8");
|
|
108
136
|
}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
// Remove system message
|
|
112
|
-
if (currentSystemMessage) {
|
|
113
|
-
try {
|
|
114
|
-
agent.removeSystemMessage(currentSystemMessage);
|
|
115
|
-
}
|
|
116
|
-
catch {
|
|
117
|
-
// Best effort — the message text may have been mutated
|
|
118
|
-
}
|
|
119
|
-
currentSystemMessage = null;
|
|
137
|
+
catch {
|
|
138
|
+
// read-only home directory --- template creation is best-effort
|
|
120
139
|
}
|
|
121
|
-
// Detach bash filter (we can't undo onBeforeToolCall, but we set a flag)
|
|
122
|
-
enabled = false;
|
|
123
|
-
(0, ui_1.updateStatusBar)("idle");
|
|
124
|
-
(0, ui_1.log)("disabled");
|
|
125
|
-
agent.displayMessage((0, ui_1.msg)("disabled"));
|
|
126
140
|
}
|
|
127
|
-
// ---------------------------------------------------------------------------
|
|
128
|
-
// Bash filter hook
|
|
129
|
-
// ---------------------------------------------------------------------------
|
|
130
141
|
/**
|
|
131
|
-
*
|
|
142
|
+
* Determine which tools should stay active after blocking read tools.
|
|
132
143
|
*
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
* - Write commands → executed directly
|
|
136
|
-
* - Ambiguous → user is prompted
|
|
144
|
+
* We MUST keep the 'subagent' tool (registered by pi-subagents) active;
|
|
145
|
+
* otherwise the orchestrator cannot call the reader at all.
|
|
137
146
|
*/
|
|
138
|
-
function
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return undefined; // undefined = proceed normally
|
|
147
|
-
const p = params;
|
|
148
|
-
const command = typeof p.command === "string" ? p.command : "";
|
|
149
|
-
if (!command)
|
|
150
|
-
return undefined; // Let the tool handle the error
|
|
151
|
-
// Classify the command
|
|
152
|
-
if ((0, bash_filter_1.isWriteCommand)(command)) {
|
|
153
|
-
// Let the raw bash/shell tool execute this directly
|
|
154
|
-
return undefined; // undefined → Pi runs the original tool
|
|
155
|
-
}
|
|
156
|
-
if ((0, bash_filter_1.isReadCommand)(command)) {
|
|
157
|
-
// Forward to Reader subagent
|
|
158
|
-
(0, ui_1.log)("reader_calling", command);
|
|
159
|
-
try {
|
|
160
|
-
const result = await (0, reader_manager_1.callReader)(agent, config, (0, bash_filter_1.wrapForReader)(command));
|
|
161
|
-
(0, ui_1.log)("reader_done");
|
|
162
|
-
// Return the result directly — Pi will use this as the tool output
|
|
163
|
-
// instead of running the original bash command.
|
|
164
|
-
return { result, subagent_used: true };
|
|
165
|
-
}
|
|
166
|
-
catch (err) {
|
|
167
|
-
(0, ui_1.logError)("reader_failed", String(err));
|
|
168
|
-
// Offer the [R/A/C] dialog
|
|
169
|
-
try {
|
|
170
|
-
const handled = await (0, reader_manager_1.handleReaderError)(agent, config, config.blocked_tools, err, (0, bash_filter_1.wrapForReader)(command), agent.promptUser);
|
|
171
|
-
// If "Allow once" was selected, return a special marker
|
|
172
|
-
if (handled.startsWith("[ALLOW_ONCE]")) {
|
|
173
|
-
return { result: handled, allow_once: true };
|
|
174
|
-
}
|
|
175
|
-
// Retry succeeded — return the result
|
|
176
|
-
return { result: handled, subagent_used: true };
|
|
177
|
-
}
|
|
178
|
-
catch (finalErr) {
|
|
179
|
-
(0, ui_1.updateStatusBar)("error");
|
|
180
|
-
return {
|
|
181
|
-
error: true,
|
|
182
|
-
message: finalErr instanceof Error
|
|
183
|
-
? finalErr.message
|
|
184
|
-
: "Reader failed",
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
// Ambiguous command → ask user
|
|
190
|
-
const answer = await agent.promptUser(`The command "${command}" may read files. Run via Reader? [Y/n]`);
|
|
191
|
-
if (answer.trim().toLowerCase() === "n" ||
|
|
192
|
-
answer.trim().toLowerCase() === "no") {
|
|
193
|
-
// Let the original tool run
|
|
194
|
-
return undefined;
|
|
195
|
-
}
|
|
196
|
-
// Forward to Reader
|
|
197
|
-
(0, ui_1.log)("reader_calling", command);
|
|
198
|
-
try {
|
|
199
|
-
const result = await (0, reader_manager_1.callReader)(agent, config, (0, bash_filter_1.wrapForReader)(command));
|
|
200
|
-
(0, ui_1.log)("reader_done");
|
|
201
|
-
return { result, subagent_used: true };
|
|
202
|
-
}
|
|
203
|
-
catch (err) {
|
|
204
|
-
(0, ui_1.logError)("reader_failed", String(err));
|
|
205
|
-
return {
|
|
206
|
-
error: true,
|
|
207
|
-
message: err instanceof Error ? err.message : "Reader failed",
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
catch {
|
|
213
|
-
// onBeforeToolCall not supported for this tool — no-op
|
|
214
|
-
}
|
|
215
|
-
}
|
|
147
|
+
function computeActiveTools(pi, blocked) {
|
|
148
|
+
const all = pi.getAllTools();
|
|
149
|
+
const blockedSet = new Set(blocked);
|
|
150
|
+
// Always keep "subagent" --- it is the bridge to the reader.
|
|
151
|
+
const forceKeep = new Set(["subagent"]);
|
|
152
|
+
return all
|
|
153
|
+
.map((t) => t.name)
|
|
154
|
+
.filter((name) => forceKeep.has(name) || !blockedSet.has(name));
|
|
216
155
|
}
|
|
217
156
|
// ---------------------------------------------------------------------------
|
|
218
|
-
//
|
|
157
|
+
// Extension entry
|
|
219
158
|
// ---------------------------------------------------------------------------
|
|
220
|
-
function
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
159
|
+
async function default_1(pi) {
|
|
160
|
+
const config = loadConfig();
|
|
161
|
+
if (!config.enabled)
|
|
162
|
+
return;
|
|
163
|
+
// --- 1. Block read tools ------------------------------------------------
|
|
164
|
+
const active = computeActiveTools(pi, config.blocked_tools);
|
|
165
|
+
pi.setActiveTools(active);
|
|
166
|
+
// --- 2. Inject orchestrator system prompt -------------------------------
|
|
167
|
+
pi.on("before_agent_start", async (event, _ctx) => {
|
|
168
|
+
return {
|
|
169
|
+
systemPrompt: `${event.systemPrompt}\n\n${config.orchestrator_prompt}`,
|
|
170
|
+
};
|
|
171
|
+
});
|
|
172
|
+
// --- 3. Intercept bash read commands ------------------------------------
|
|
173
|
+
//
|
|
174
|
+
// When the LLM tries `cat some-file` or similar, we block the call and
|
|
175
|
+
// tell it to route through the reader subagent instead.
|
|
176
|
+
pi.on("tool_call", async (event, _ctx) => {
|
|
177
|
+
if (event.toolName === "bash" || event.toolName === "shell") {
|
|
178
|
+
const command = String(event.input?.command ?? "");
|
|
179
|
+
const firstWord = command.trim().split(/\s+/)[0]?.toLowerCase() ?? "";
|
|
180
|
+
if (READ_BASH_COMMANDS.has(firstWord)) {
|
|
181
|
+
return {
|
|
182
|
+
block: true,
|
|
183
|
+
reason: [
|
|
184
|
+
`Use subagent(agent: "reader", task: "Execute and summarize: ${command}")`,
|
|
185
|
+
"instead of running file-reading commands directly.",
|
|
186
|
+
].join(" "),
|
|
187
|
+
};
|
|
233
188
|
}
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
// --- 4. Register /read-delegator command --------------------------------
|
|
192
|
+
pi.registerCommand("read-delegator", {
|
|
193
|
+
description: "Manage read delegation (on|off|status)",
|
|
194
|
+
handler: async (args, ctx) => {
|
|
195
|
+
const sub = args?.trim().toLowerCase() ?? "status";
|
|
196
|
+
switch (sub) {
|
|
197
|
+
case "on":
|
|
198
|
+
case "enable": {
|
|
199
|
+
config.enabled = true;
|
|
200
|
+
saveConfig(config);
|
|
201
|
+
const active2 = computeActiveTools(pi, config.blocked_tools);
|
|
202
|
+
pi.setActiveTools(active2);
|
|
203
|
+
ctx.ui.notify("🟢 Read delegation enabled", "info");
|
|
204
|
+
return;
|
|
205
|
+
}
|
|
206
|
+
case "off":
|
|
207
|
+
case "disable": {
|
|
237
208
|
config.enabled = false;
|
|
238
|
-
|
|
209
|
+
saveConfig(config);
|
|
210
|
+
// Restore all tools
|
|
211
|
+
pi.setActiveTools(pi.getAllTools().map((t) => t.name));
|
|
212
|
+
ctx.ui.notify("🔴 Read delegation disabled", "info");
|
|
213
|
+
ctx.ui.setStatus("read-delegator", undefined);
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
case "status":
|
|
217
|
+
default: {
|
|
218
|
+
const lines = [
|
|
219
|
+
`Read delegation: ${config.enabled ? "🟢 enabled" : "🔴 disabled"}`,
|
|
220
|
+
`Blocked tools: ${config.blocked_tools.join(", ")}`,
|
|
221
|
+
`Reader subagent: ${config.reader_subagent_name}`,
|
|
222
|
+
];
|
|
223
|
+
ctx.ui.notify(lines.join("\n"), "info");
|
|
224
|
+
return;
|
|
239
225
|
}
|
|
240
|
-
doDisable(agent);
|
|
241
|
-
return (0, ui_1.msg)("disabled");
|
|
242
|
-
}
|
|
243
|
-
case "status": {
|
|
244
|
-
const status = (0, ui_1.getStatus)();
|
|
245
|
-
const blocked = (0, tool_blocker_1.getBlockedTools)();
|
|
246
|
-
return (`pi-read-delegator is ${status}\n` +
|
|
247
|
-
`Enabled: ${enabled ? "yes" : "no"}\n` +
|
|
248
|
-
`Blocked tools: ${blocked.join(", ") || "(none)"}\n` +
|
|
249
|
-
`Reader subagent: ${config?.reader_subagent_name ?? "reader"}\n` +
|
|
250
|
-
`Language: ${config?.language ?? "auto"}`);
|
|
251
226
|
}
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
227
|
+
},
|
|
228
|
+
});
|
|
229
|
+
// --- 5. Ensure reader.md template ---------------------------------------
|
|
230
|
+
await ensureReaderTemplate();
|
|
231
|
+
// --- 6. Status bar ------------------------------------------------------
|
|
232
|
+
pi.on("session_start", async (_event, ctx) => {
|
|
233
|
+
ctx.ui.setStatus("read-delegator", `● reader: ${config.reader_subagent_name}`);
|
|
258
234
|
});
|
|
259
235
|
}
|
|
260
236
|
//# sourceMappingURL=index.js.map
|
package/package.json
CHANGED