@juicesharp/rpiv-args 1.1.5 → 1.2.0
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/args.ts +51 -14
- package/index.ts +3 -2
- package/package.json +1 -1
package/args.ts
CHANGED
|
@@ -1,19 +1,33 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* rpiv-args — core logic.
|
|
3
3
|
*
|
|
4
|
-
* Intercepts `/skill:<name> <args>` at the input hook and emits a
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
4
|
+
* Intercepts `/skill:<name> <args>` at the input hook and emits a Pi skill
|
|
5
|
+
* wrapper with opt-in $N/$ARGUMENTS/$@/${@:N[:L]} substitution on the body.
|
|
6
|
+
* Two emit paths:
|
|
7
|
+
* - No-token path: byte-identical to Pi's built-in `_expandSkillCommand`
|
|
8
|
+
* output (wrapper + `\n\n${args}` suffix), preserving full backward
|
|
9
|
+
* compatibility for skills without placeholders.
|
|
10
|
+
* - Token path: substitutes inside the body and INTENTIONALLY drops the
|
|
11
|
+
* trailing `\n\n${args}` suffix. The bare imperative outside the block
|
|
12
|
+
* hijacks LLM attention from the skill workflow; inside-only emission
|
|
13
|
+
* leaves the skill body as the sole user-message payload competing for
|
|
14
|
+
* attention. See architecture.md "System-Prompt Protocol & Token-Path
|
|
15
|
+
* Divergence".
|
|
16
|
+
*
|
|
17
|
+
* Also prepends a skill-invocation protocol to the system prompt every turn
|
|
18
|
+
* (via before_agent_start) so the LLM treats trailing text after `</skill>`
|
|
19
|
+
* as the skill's argument input rather than a separate imperative.
|
|
9
20
|
*
|
|
10
21
|
* Byte-exact wrapper requirement: parseSkillBlock regex at
|
|
11
22
|
* node_modules/@mariozechner/pi-coding-agent/dist/core/agent-session.js:40
|
|
12
|
-
* is the load-bearing contract. Do not reformat the
|
|
23
|
+
* is the load-bearing contract for the wrapper itself. Do not reformat the
|
|
24
|
+
* template literal below.
|
|
13
25
|
*/
|
|
14
26
|
|
|
15
27
|
import { readFileSync } from "node:fs";
|
|
16
28
|
import {
|
|
29
|
+
type BeforeAgentStartEvent,
|
|
30
|
+
type BeforeAgentStartEventResult,
|
|
17
31
|
type ExtensionAPI,
|
|
18
32
|
getAgentDir,
|
|
19
33
|
type InputEvent,
|
|
@@ -29,13 +43,13 @@ import {
|
|
|
29
43
|
// ---------------------------------------------------------------------------
|
|
30
44
|
|
|
31
45
|
/** Matches any placeholder Pi's substituteArgs would replace. Used as the
|
|
32
|
-
* opt-in gate: absent → pass through verbatim
|
|
46
|
+
* opt-in gate: absent → pass through verbatim. */
|
|
33
47
|
const TOKEN_REGEX = /\$(?:\d+|ARGUMENTS|@|\{@:\d+(?::\d+)?\})/;
|
|
34
48
|
|
|
35
|
-
/** Prefix Pi uses (`agent-session.js:829`). Single-space tokenisation
|
|
49
|
+
/** Prefix Pi uses (`agent-session.js:829`). Single-space tokenisation. */
|
|
36
50
|
const SKILL_PREFIX = "/skill:";
|
|
37
51
|
|
|
38
|
-
/** Re-entrancy guard
|
|
52
|
+
/** Re-entrancy guard. */
|
|
39
53
|
const WRAPPED_PREFIX = "<skill ";
|
|
40
54
|
|
|
41
55
|
// ---------------------------------------------------------------------------
|
|
@@ -151,13 +165,13 @@ function appendArgs(skillBlock: string, args: string): string {
|
|
|
151
165
|
export function handleInput(event: InputEvent): InputEventResult {
|
|
152
166
|
const text = event.text;
|
|
153
167
|
|
|
154
|
-
//
|
|
168
|
+
// Re-entrancy: already-wrapped text (from our own or any other
|
|
155
169
|
// extension's {action:"transform"}) passes through untouched.
|
|
156
170
|
if (text.startsWith(WRAPPED_PREFIX)) return { action: "continue" };
|
|
157
171
|
|
|
158
172
|
if (!text.startsWith(SKILL_PREFIX)) return { action: "continue" };
|
|
159
173
|
|
|
160
|
-
//
|
|
174
|
+
// Single-space tokenisation — byte-match Pi's indexOf(" ") at :831.
|
|
161
175
|
const spaceIndex = text.indexOf(" ");
|
|
162
176
|
const skillName = spaceIndex === -1 ? text.slice(SKILL_PREFIX.length) : text.slice(SKILL_PREFIX.length, spaceIndex);
|
|
163
177
|
const argsString = spaceIndex === -1 ? "" : text.slice(spaceIndex + 1).trim();
|
|
@@ -173,17 +187,39 @@ export function handleInput(event: InputEvent): InputEventResult {
|
|
|
173
187
|
}
|
|
174
188
|
|
|
175
189
|
const { frontmatter } = parseFrontmatter<{ "argument-hint"?: string }>(content);
|
|
176
|
-
void frontmatter; // informational only in v1
|
|
190
|
+
void frontmatter; // informational only in v1
|
|
177
191
|
const body = stripFrontmatter(content).trim();
|
|
178
192
|
|
|
179
|
-
//
|
|
193
|
+
// Opt-in gate: if body has no token, emit byte-identical to Pi's :841.
|
|
180
194
|
if (!TOKEN_REGEX.test(body)) {
|
|
181
195
|
return { action: "transform", text: appendArgs(buildSkillBlock(entry, body), argsString) };
|
|
182
196
|
}
|
|
183
197
|
|
|
184
198
|
const parsed = parseCommandArgs(argsString);
|
|
185
199
|
const substituted = substituteArgs(body, parsed);
|
|
186
|
-
|
|
200
|
+
// Substitution consumes the args — do not also append them after </skill>.
|
|
201
|
+
// Bare trailing imperatives hijack LLM attention from the skill body. See architecture.md.
|
|
202
|
+
return { action: "transform", text: buildSkillBlock(entry, substituted) };
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
// ---------------------------------------------------------------------------
|
|
206
|
+
// Skill-invocation protocol — prepended to the system prompt every turn via
|
|
207
|
+
// before_agent_start. See architecture.md for rationale and re-application
|
|
208
|
+
// semantics (agent-session.js:112-113 — Pi's canonical per-turn pattern).
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
|
|
211
|
+
export const SKILL_INVOCATION_PROTOCOL = `## Skill invocation protocol (CRITICAL)
|
|
212
|
+
|
|
213
|
+
A \`<skill name="..." location="...">...</skill>\` block in a user message is a structured invocation. Handle it as follows:
|
|
214
|
+
|
|
215
|
+
1. The block body defines the workflow you must execute. Follow it.
|
|
216
|
+
2. Any text after \`</skill>\` is the user's argument input to that skill — never a separate command, even when it reads as an imperative ("create X", "update Y", "delete Z").
|
|
217
|
+
3. Do not bypass the skill's workflow to act on trailing text directly. The user invoked the skill because they want the skill's workflow applied to that input.
|
|
218
|
+
|
|
219
|
+
`;
|
|
220
|
+
|
|
221
|
+
export function handleBeforeAgentStart(event: BeforeAgentStartEvent): BeforeAgentStartEventResult {
|
|
222
|
+
return { systemPrompt: SKILL_INVOCATION_PROTOCOL + event.systemPrompt };
|
|
187
223
|
}
|
|
188
224
|
|
|
189
225
|
// ---------------------------------------------------------------------------
|
|
@@ -192,6 +228,7 @@ export function handleInput(event: InputEvent): InputEventResult {
|
|
|
192
228
|
|
|
193
229
|
export function registerArgsHandler(pi: ExtensionAPI): void {
|
|
194
230
|
pi.on("input", (event) => handleInput(event));
|
|
231
|
+
pi.on("before_agent_start", (event) => handleBeforeAgentStart(event));
|
|
195
232
|
pi.on("session_start", (event) => {
|
|
196
233
|
if (event.reason === "reload" || event.reason === "startup") {
|
|
197
234
|
invalidateSkillIndex();
|
package/index.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* rpiv-args — Pi extension entry point.
|
|
3
3
|
*
|
|
4
|
-
* Registers the `input` event handler
|
|
5
|
-
* All logic lives in
|
|
4
|
+
* Registers the `input` event handler, a `before_agent_start` system-prompt
|
|
5
|
+
* augmenter, and a `session_start` cache invalidator. All logic lives in
|
|
6
|
+
* args.ts.
|
|
6
7
|
*/
|
|
7
8
|
|
|
8
9
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|