pi-live-context 0.0.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/CHANGELOG.md ADDED
@@ -0,0 +1 @@
1
+ # pi-live-context
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Braden Lamb
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,40 @@
1
+ # pi-live-context
2
+
3
+ A [Pi](https://pi.dev) extension that reloads `AGENTS.md` every turn and sends it as a user message instead of system prompt text.
4
+
5
+ ## Install
6
+
7
+ ```bash
8
+ # from npm
9
+ pi install npm:pi-live-context
10
+
11
+ # or from git
12
+ pi install git:github.com/bradennss/pi-live-context
13
+
14
+ # try it for a single run without installing
15
+ pi -e npm:pi-live-context
16
+ ```
17
+
18
+ ## How it works
19
+
20
+ Pi discovers `AGENTS.md` and `CLAUDE.md` the same way it usually does, but sends it just before your message instead of including it in the system prompt. This makes agents follow global and project instructions more reliably, with the tradeoff of consuming more input tokens over time.
21
+
22
+ ## Requirements
23
+
24
+ - Node.js >= 20.
25
+ - Pi 0.85 or newer.
26
+
27
+ ## Development
28
+
29
+ ```bash
30
+ pnpm install
31
+ pnpm run check
32
+ ```
33
+
34
+ ## Contributing
35
+
36
+ Every change that affects the published package needs a [changeset](https://github.com/changesets/changesets):
37
+
38
+ ```bash
39
+ pnpm changeset
40
+ ```
package/SECURITY.md ADDED
@@ -0,0 +1,13 @@
1
+ # Security Policy
2
+
3
+ ## Scope and behavior
4
+
5
+ `pi-live-context` is a Pi extension. Like all Pi extensions it runs with your full user permissions. This extension:
6
+
7
+ - Reads the `AGENTS.md` and `CLAUDE.md` files Pi already discovers, through Pi's own `loadProjectContextFiles`, once per provider request.
8
+ - Removes that content from the system prompt and sends it as a hidden message instead. The content still goes to your model provider, in the same request it would have gone in before.
9
+ - Writes nothing to disk, makes no network calls of its own, collects no telemetry, and reads no credentials. It has no runtime dependencies beyond Pi itself.
10
+
11
+ ## Reporting a vulnerability
12
+
13
+ Please report suspected vulnerabilities privately via a [GitHub security advisory](https://github.com/bradennss/pi-live-context/security/advisories/new). You will receive an acknowledgement within a few days.
package/index.ts ADDED
@@ -0,0 +1,59 @@
1
+ /**
2
+ * pi-live-context takes the AGENTS.md files out of the system prompt and sends
3
+ * them as a hidden message before the newest user message, reloaded from disk
4
+ * before every request.
5
+ */
6
+ import {
7
+ type ExtensionAPI,
8
+ type ExtensionContext,
9
+ getAgentDir,
10
+ loadProjectContextFiles,
11
+ } from "@earendil-works/pi-coding-agent";
12
+ import {
13
+ buildContextMessage,
14
+ insertBeforeLastUserMessage,
15
+ } from "./src/context-message.ts";
16
+ import { stripContextFiles } from "./src/system-prompt.ts";
17
+
18
+ const LAYOUT_WARNING =
19
+ "pi-live-context is off for this session: this version of Pi lays out context files in a way the extension cannot remove from the system prompt.";
20
+
21
+ export default function liveContext(pi: ExtensionAPI): void {
22
+ let relocating = false;
23
+ let warned = false;
24
+
25
+ function warnOnce(ctx: ExtensionContext): void {
26
+ if (warned) {
27
+ return;
28
+ }
29
+ warned = true;
30
+ ctx.ui.notify(LAYOUT_WARNING, "warning");
31
+ }
32
+
33
+ pi.on("before_agent_start", (event, ctx) => {
34
+ const contextFiles = event.systemPromptOptions.contextFiles ?? [];
35
+ const systemPrompt = stripContextFiles(event.systemPrompt, contextFiles);
36
+ relocating = contextFiles.length > 0 && systemPrompt !== event.systemPrompt;
37
+ if (contextFiles.length > 0 && !relocating) {
38
+ warnOnce(ctx);
39
+ }
40
+ return relocating ? { systemPrompt } : undefined;
41
+ });
42
+
43
+ pi.on("context", (event, ctx) => {
44
+ if (!relocating) {
45
+ return;
46
+ }
47
+ const agentDir = getAgentDir();
48
+ const contextFiles = loadProjectContextFiles({ cwd: ctx.cwd, agentDir });
49
+ if (contextFiles.length === 0) {
50
+ return;
51
+ }
52
+ return {
53
+ messages: insertBeforeLastUserMessage(
54
+ event.messages,
55
+ buildContextMessage(contextFiles, agentDir),
56
+ ),
57
+ };
58
+ });
59
+ }
package/package.json ADDED
@@ -0,0 +1,74 @@
1
+ {
2
+ "name": "pi-live-context",
3
+ "version": "0.0.1",
4
+ "description": "Pi extension that reloads AGENTS.md every turn and sends it as a user message instead of system prompt text.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Braden Lamb <hi@braden.lol>",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/bradennss/pi-live-context.git"
11
+ },
12
+ "homepage": "https://github.com/bradennss/pi-live-context#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/bradennss/pi-live-context/issues"
15
+ },
16
+ "keywords": [
17
+ "pi-package",
18
+ "pi",
19
+ "pi-extension",
20
+ "pi-coding-agent",
21
+ "agents-md",
22
+ "context",
23
+ "system-prompt"
24
+ ],
25
+ "exports": {
26
+ ".": {
27
+ "types": "./index.ts",
28
+ "import": "./index.ts",
29
+ "default": "./index.ts"
30
+ }
31
+ },
32
+ "files": [
33
+ "index.ts",
34
+ "src",
35
+ "README.md",
36
+ "LICENSE",
37
+ "SECURITY.md",
38
+ "CHANGELOG.md"
39
+ ],
40
+ "engines": {
41
+ "node": ">=20"
42
+ },
43
+ "pi": {
44
+ "extensions": [
45
+ "./index.ts"
46
+ ]
47
+ },
48
+ "peerDependencies": {
49
+ "@earendil-works/pi-coding-agent": "*"
50
+ },
51
+ "devDependencies": {
52
+ "@changesets/changelog-github": "^1.0.0",
53
+ "@changesets/cli": "^3.0.1",
54
+ "@earendil-works/pi-coding-agent": "^0.85.1",
55
+ "@eslint/js": "^10.0.1",
56
+ "@types/node": "^22.0.0",
57
+ "eslint": "^10.8.1",
58
+ "eslint-config-prettier": "^10.1.8",
59
+ "husky": "^9.1.7",
60
+ "prettier": "^3.9.6",
61
+ "typescript": "^5.6.0",
62
+ "typescript-eslint": "^8.67.0",
63
+ "vitest": "^4.1.11"
64
+ },
65
+ "scripts": {
66
+ "typecheck": "tsc --noEmit",
67
+ "lint": "eslint . --max-warnings 0",
68
+ "format": "prettier --write .",
69
+ "format:check": "prettier --check .",
70
+ "test": "vitest run",
71
+ "test:watch": "vitest",
72
+ "check": "pnpm run typecheck && pnpm run lint && pnpm run format:check && pnpm test"
73
+ }
74
+ }
@@ -0,0 +1,4 @@
1
+ export interface ContextFile {
2
+ path: string;
3
+ content: string;
4
+ }
@@ -0,0 +1,83 @@
1
+ import path from "node:path";
2
+ import type { ContextEvent } from "@earendil-works/pi-coding-agent";
3
+ import type { ContextFile } from "./context-file.ts";
4
+
5
+ export const CONTEXT_MESSAGE_TYPE = "live-context";
6
+
7
+ type AgentMessage = ContextEvent["messages"][number];
8
+ type CustomMessage = Extract<AgentMessage, { role: "custom" }>;
9
+
10
+ function isGlobal(file: ContextFile, agentDir: string): boolean {
11
+ return path.resolve(path.dirname(file.path)) === path.resolve(agentDir);
12
+ }
13
+
14
+ function renderBlock(
15
+ tag: string,
16
+ heading: string,
17
+ contextFiles: ContextFile[],
18
+ ): string {
19
+ const files = contextFiles
20
+ .map(
21
+ ({ path: filePath, content }) =>
22
+ `<instructions path="${filePath}">\n${content}\n</instructions>`,
23
+ )
24
+ .join("\n\n");
25
+ return `<${tag}>\n\n${heading}\n\n${files}\n\n</${tag}>`;
26
+ }
27
+
28
+ export function buildContextMessageContent(
29
+ contextFiles: ContextFile[],
30
+ agentDir: string,
31
+ ): string {
32
+ const global = contextFiles.filter((file) => isGlobal(file, agentDir));
33
+ const project = contextFiles.filter((file) => !isGlobal(file, agentDir));
34
+ const blocks: string[] = [];
35
+ if (global.length > 0) {
36
+ blocks.push(
37
+ renderBlock(
38
+ "global_context",
39
+ "Global instructions and guidelines:",
40
+ global,
41
+ ),
42
+ );
43
+ }
44
+ if (project.length > 0) {
45
+ blocks.push(
46
+ renderBlock(
47
+ "project_context",
48
+ "Project-specific instructions and guidelines:",
49
+ project,
50
+ ),
51
+ );
52
+ }
53
+ return blocks.join("\n\n");
54
+ }
55
+
56
+ export function buildContextMessage(
57
+ contextFiles: ContextFile[],
58
+ agentDir: string,
59
+ timestamp: number = Date.now(),
60
+ ): CustomMessage {
61
+ return {
62
+ role: "custom",
63
+ customType: CONTEXT_MESSAGE_TYPE,
64
+ content: buildContextMessageContent(contextFiles, agentDir),
65
+ display: false,
66
+ timestamp,
67
+ };
68
+ }
69
+
70
+ export function insertBeforeLastUserMessage<M extends { role: string }>(
71
+ messages: M[],
72
+ message: M,
73
+ ): M[] {
74
+ const result = [...messages];
75
+ for (let index = result.length - 1; index >= 0; index--) {
76
+ if (result[index].role === "user") {
77
+ result.splice(index, 0, message);
78
+ return result;
79
+ }
80
+ }
81
+ result.push(message);
82
+ return result;
83
+ }
@@ -0,0 +1,22 @@
1
+ import type { ContextFile } from "./context-file.ts";
2
+
3
+ /** Mirrors the `<project_context>` block `buildSystemPrompt` appends for context files. */
4
+ function renderSystemPromptBlock(contextFiles: ContextFile[]): string {
5
+ let block = "\n\n<project_context>\n\n";
6
+ block += "Project-specific instructions and guidelines:\n\n";
7
+ for (const { path, content } of contextFiles) {
8
+ block += `<project_instructions path="${path}">\n${content}\n</project_instructions>\n\n`;
9
+ }
10
+ block += "</project_context>\n";
11
+ return block;
12
+ }
13
+
14
+ export function stripContextFiles(
15
+ systemPrompt: string,
16
+ contextFiles: ContextFile[],
17
+ ): string {
18
+ if (contextFiles.length === 0) {
19
+ return systemPrompt;
20
+ }
21
+ return systemPrompt.replace(renderSystemPromptBlock(contextFiles), "");
22
+ }