pi-runline 0.4.0 → 0.5.1
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/README.md +25 -6
- package/extensions/runline-context/index.ts +31 -82
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Code mode for [pi](https://github.com/mariozechner/pi).
|
|
4
4
|
|
|
5
|
-
An extension that plugs [runline](https://www.npmjs.com/package/runline) into coding agents. The agent gets
|
|
5
|
+
An extension that plugs [runline](https://www.npmjs.com/package/runline) into coding agents. The agent gets one native tool, in-sandbox action discovery, a fuzzy picker for choosing which of the 188 built-in plugins to expose, and a guided credential prompt for the ones it hasn't seen before.
|
|
6
6
|
|
|
7
7
|
## Install
|
|
8
8
|
|
|
@@ -10,13 +10,32 @@ An extension that plugs [runline](https://www.npmjs.com/package/runline) into co
|
|
|
10
10
|
pi install pi-runline
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
##
|
|
13
|
+
## How the agent uses it
|
|
14
14
|
|
|
15
|
-
On session start, if the current working directory has a `.runline/` (or one is configured globally — see below), the extension registers
|
|
15
|
+
On session start, if the current working directory has a `.runline/` (or one is configured globally — see below), the extension registers a single tool and injects a short primer naming the enabled plugins:
|
|
16
16
|
|
|
17
|
-
- **`list_runline_actions`** — enumerate available actions with input schemas. Optional `plugin` filter.
|
|
18
17
|
- **`execute_runline`** — run JavaScript in runline's QuickJS sandbox. Every enabled plugin is a top-level global; `return` surfaces the result; logs are captured.
|
|
19
18
|
|
|
19
|
+
Discovery happens **inside the sandbox**, not as a separate tool. The agent uses an `actions` object to explore the catalog without paying for a full schema dump in its system prompt:
|
|
20
|
+
|
|
21
|
+
```js
|
|
22
|
+
actions.list() // every "plugin.action" path
|
|
23
|
+
actions.list("github") // filter to one plugin
|
|
24
|
+
actions.find("create issue") // ranked fuzzy search (MiniSearch)
|
|
25
|
+
actions.describe("github.issue.create")
|
|
26
|
+
// → { path, plugin, action, description, signature, inputs }
|
|
27
|
+
actions.check("github.issue.create", { owner: "a" })
|
|
28
|
+
// → { ok, missing, unknown, typeErrors, signature } (does NOT call the action)
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Unknown paths throw with did-you-mean suggestions, so typos are self-correcting. Recommended flow: `find` → `describe` → `check` → call.
|
|
32
|
+
|
|
33
|
+
Calling actions is unchanged — plugin globals and `actions.<plugin>.<action>(...)` are the same call:
|
|
34
|
+
|
|
35
|
+
```js
|
|
36
|
+
return await github.issue.create({ owner: "acme", repo: "api", title: "Bug" });
|
|
37
|
+
```
|
|
38
|
+
|
|
20
39
|
## `/runline-plugins` — the picker
|
|
21
40
|
|
|
22
41
|
Typing `/runline-plugins` in a pi session opens a fuzzy multi-select over all 188 built-in plugins.
|
|
@@ -66,6 +85,6 @@ Fallback used when the current cwd has no `.runline/` anywhere up the tree. Usef
|
|
|
66
85
|
|
|
67
86
|
## How plugin allow-listing works
|
|
68
87
|
|
|
69
|
-
The extension deliberately exposes nothing by default. That's on purpose — 2,410 actions is a lot of context budget. You pick the plugins that matter for a given project, commit the allowlist to `.runline/config.json`, and the agent only ever sees
|
|
88
|
+
The extension deliberately exposes nothing by default. That's on purpose — 2,410 actions is a lot of context budget. You pick the plugins that matter for a given project, commit the allowlist to `.runline/config.json`, and the agent only ever sees the ones you enabled in its primer.
|
|
70
89
|
|
|
71
|
-
Note that the QuickJS sandbox itself still registers every runline plugin as a global, so in principle an agent could guess and call a disabled plugin. In practice the
|
|
90
|
+
Note that the QuickJS sandbox itself still registers every runline plugin as a global (and `actions.list()` will surface them all), so in principle an agent could guess and call a disabled plugin. In practice the primer only advertises the allowlisted ones, and unconfigured plugins error out at first action call anyway. Plumbing the allowlist into the sandbox registry is on the roadmap.
|
|
@@ -10,16 +10,6 @@ import {
|
|
|
10
10
|
savePiPlugins,
|
|
11
11
|
} from "../runline-resolve.js";
|
|
12
12
|
|
|
13
|
-
type ActionEntry = {
|
|
14
|
-
plugin: string;
|
|
15
|
-
action: string;
|
|
16
|
-
description?: string;
|
|
17
|
-
inputSchema?: Record<
|
|
18
|
-
string,
|
|
19
|
-
{ type: string; required?: boolean; description?: string }
|
|
20
|
-
>;
|
|
21
|
-
};
|
|
22
|
-
|
|
23
13
|
function filterByAllowlist<T extends { name?: string; plugin?: string }>(
|
|
24
14
|
items: T[],
|
|
25
15
|
allow: string[] | undefined,
|
|
@@ -29,34 +19,6 @@ function filterByAllowlist<T extends { name?: string; plugin?: string }>(
|
|
|
29
19
|
return items.filter((i) => set.has((i.name ?? i.plugin) as string));
|
|
30
20
|
}
|
|
31
21
|
|
|
32
|
-
function formatActions(actions: ActionEntry[]): string {
|
|
33
|
-
const grouped = new Map<string, ActionEntry[]>();
|
|
34
|
-
for (const a of actions) {
|
|
35
|
-
const list = grouped.get(a.plugin) ?? [];
|
|
36
|
-
list.push(a);
|
|
37
|
-
grouped.set(a.plugin, list);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const lines: string[] = [];
|
|
41
|
-
for (const [plugin, entries] of grouped) {
|
|
42
|
-
lines.push(`### ${plugin}`);
|
|
43
|
-
for (const a of entries) {
|
|
44
|
-
const inputs = a.inputSchema
|
|
45
|
-
? Object.entries(a.inputSchema)
|
|
46
|
-
.map(([k, v]) => `${k}: ${v.type}${v.required ? "" : "?"}`)
|
|
47
|
-
.join(", ")
|
|
48
|
-
: "";
|
|
49
|
-
const sig = inputs
|
|
50
|
-
? `\`${plugin}.${a.action}({ ${inputs} })\``
|
|
51
|
-
: `\`${plugin}.${a.action}()\``;
|
|
52
|
-
const desc = a.description ? ` — ${a.description}` : "";
|
|
53
|
-
lines.push(`- ${sig}${desc}`);
|
|
54
|
-
}
|
|
55
|
-
lines.push("");
|
|
56
|
-
}
|
|
57
|
-
return lines.join("\n");
|
|
58
|
-
}
|
|
59
|
-
|
|
60
22
|
const runlineCache = new Map<string, Promise<Runline>>();
|
|
61
23
|
|
|
62
24
|
async function getRunline(cwd: string): Promise<Runline> {
|
|
@@ -157,21 +119,39 @@ export default function (pi: ExtensionAPI) {
|
|
|
157
119
|
);
|
|
158
120
|
|
|
159
121
|
if (!alreadyInjected) {
|
|
160
|
-
const
|
|
161
|
-
|
|
162
|
-
"
|
|
163
|
-
|
|
164
|
-
|
|
122
|
+
const pluginList = plugins
|
|
123
|
+
.map((p) => `\`${p.name}\` (${p.actions.length})`)
|
|
124
|
+
.join(", ");
|
|
125
|
+
|
|
126
|
+
const content =
|
|
127
|
+
"## Runline\n\n" +
|
|
128
|
+
`This project has runline installed with **${plugins.length} plugins, ${actions.length} actions**. ` +
|
|
129
|
+
"Use the `execute_runline` tool to run JavaScript in a sandbox where each plugin is a top-level global. " +
|
|
165
130
|
"Chain actions, await results, return a value.\n\n" +
|
|
166
|
-
|
|
167
|
-
"
|
|
131
|
+
`**Enabled plugins:** ${pluginList}\n\n` +
|
|
132
|
+
"### Discovering actions\n\n" +
|
|
133
|
+
"Inside the sandbox, an `actions` object lets you explore the catalog without leaving `execute_runline`. " +
|
|
134
|
+
"Prefer this over guessing — it's how you find the right action and verify call shapes before invoking.\n\n" +
|
|
135
|
+
"```js\n" +
|
|
136
|
+
'actions.list() // every "plugin.action" path\n' +
|
|
137
|
+
'actions.list("github") // just one plugin\n' +
|
|
138
|
+
'actions.find("create issue") // ranked fuzzy search — [{path, description, score}]\n' +
|
|
139
|
+
'actions.describe("github.issue.create")\n' +
|
|
140
|
+
"// → { path, plugin, action, description, signature, inputs }\n" +
|
|
141
|
+
'actions.check("github.issue.create", { owner: "a" })\n' +
|
|
142
|
+
"// → { ok, missing, unknown, typeErrors, signature } (does NOT call the action)\n" +
|
|
143
|
+
"```\n\n" +
|
|
144
|
+
"Unknown paths throw with did-you-mean suggestions, so typos are self-correcting. " +
|
|
145
|
+
"Recommended flow: `find` → `describe` → `check` → call.\n\n" +
|
|
146
|
+
"### Calling actions\n\n" +
|
|
168
147
|
"```js\n" +
|
|
169
148
|
'return await github.issue.create({ owner: "acme", repo: "api", title: "Bug" })\n' +
|
|
170
|
-
"```\n\n"
|
|
149
|
+
"```\n\n" +
|
|
150
|
+
"Plugin globals (`github`, `slack`, ...) and `actions.<plugin>.<action>(...)` both work — same call.\n";
|
|
171
151
|
|
|
172
152
|
pi.sendMessage({
|
|
173
153
|
customType: "runline-context",
|
|
174
|
-
content
|
|
154
|
+
content,
|
|
175
155
|
display: true,
|
|
176
156
|
});
|
|
177
157
|
}
|
|
@@ -205,9 +185,10 @@ export default function (pi: ExtensionAPI) {
|
|
|
205
185
|
const rl = await getRunline(ctx.cwd);
|
|
206
186
|
// Note: the sandbox currently exposes every registered plugin as a
|
|
207
187
|
// global. The allowlist drives what the agent is told about in its
|
|
208
|
-
// injected context and
|
|
209
|
-
//
|
|
210
|
-
// allowlist through to the sandbox
|
|
188
|
+
// injected context (and what `actions.list()` surfaces in practice,
|
|
189
|
+
// since the agent only knows to look for what was advertised).
|
|
190
|
+
// Plumbing the allowlist through to the sandbox registry is a
|
|
191
|
+
// future improvement.
|
|
211
192
|
const result = await rl.execute(params.code);
|
|
212
193
|
|
|
213
194
|
const logs = result.logs?.length
|
|
@@ -234,38 +215,6 @@ export default function (pi: ExtensionAPI) {
|
|
|
234
215
|
},
|
|
235
216
|
});
|
|
236
217
|
|
|
237
|
-
pi.registerTool({
|
|
238
|
-
name: "list_runline_actions",
|
|
239
|
-
label: "Runline Actions",
|
|
240
|
-
description:
|
|
241
|
-
"List every available runline action with its plugin, description, and input schema.",
|
|
242
|
-
promptSnippet:
|
|
243
|
-
"Discover runline plugin actions and their input shapes before calling execute_runline",
|
|
244
|
-
parameters: Type.Object({
|
|
245
|
-
plugin: Type.Optional(
|
|
246
|
-
Type.String({
|
|
247
|
-
description: "Filter to a single plugin (e.g. 'github')",
|
|
248
|
-
}),
|
|
249
|
-
),
|
|
250
|
-
}),
|
|
251
|
-
async execute(_toolCallId, params, _signal, _onUpdate, ctx) {
|
|
252
|
-
const rl = await getRunline(ctx.cwd);
|
|
253
|
-
const runlineDir = findRunlineDir(ctx.cwd);
|
|
254
|
-
const allow = runlineDir
|
|
255
|
-
? loadExtConfig(runlineDir).piPlugins
|
|
256
|
-
: undefined;
|
|
257
|
-
let actions = filterByAllowlist(rl.actions(), allow);
|
|
258
|
-
if (params.plugin) {
|
|
259
|
-
actions = actions.filter((a) => a.plugin === params.plugin);
|
|
260
|
-
}
|
|
261
|
-
const text = formatActions(actions);
|
|
262
|
-
return {
|
|
263
|
-
content: [{ type: "text", text: text || "No actions enabled." }],
|
|
264
|
-
details: { actions },
|
|
265
|
-
};
|
|
266
|
-
},
|
|
267
|
-
});
|
|
268
|
-
|
|
269
218
|
// ── Commands ────────────────────────────────────────────────────
|
|
270
219
|
|
|
271
220
|
pi.registerCommand("runline-plugins", {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-runline",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.5.1",
|
|
4
4
|
"description": "Code mode for pi",
|
|
5
5
|
"type": "commonjs",
|
|
6
6
|
"keywords": [
|
|
@@ -22,7 +22,7 @@
|
|
|
22
22
|
"lint:fix": "biome check --write extensions/"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"runline": "^0.
|
|
25
|
+
"runline": "^0.5.1"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@mariozechner/pi-coding-agent": "*",
|