@vanillagreen/pi-claude-bridge 1.1.3 → 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.
@@ -76,8 +76,15 @@ function extractHeadingSection(systemPrompt: string | undefined, headings: strin
76
76
  }
77
77
  if (start < 0) return undefined;
78
78
  const rest = systemPrompt.slice(start).trim();
79
- const nextHeading = rest.slice(1).search(/\n##\s+/);
80
- return (nextHeading >= 0 ? rest.slice(0, nextHeading + 1) : rest).trim();
79
+ const endCandidates = [
80
+ rest.slice(1).search(/\n##\s+/),
81
+ rest.search(/\n<\/project_instructions>/),
82
+ rest.search(/\n<\/project_context>/),
83
+ ]
84
+ .map((index, offset) => (index >= 0 && offset === 0 ? index + 1 : index))
85
+ .filter((index) => index >= 0);
86
+ const end = endCandidates.length > 0 ? Math.min(...endCandidates) : -1;
87
+ return (end >= 0 ? rest.slice(0, end) : rest).trim();
81
88
  }
82
89
 
83
90
  function extractBlockByMarkers(systemPrompt: string | undefined, markers: RegExp[]): string | undefined {
@@ -93,7 +100,7 @@ export function buildPromptContextAppend(systemPrompt: string | undefined, cwd:
93
100
 
94
101
  if (settings.includeAppendSystemPromptMd) {
95
102
  for (const file of readAppendSystemPromptFiles(cwd)) {
96
- parts.push(`### ${file.label}\n\n${file.content}`);
103
+ parts.push(xmlBlock("append_system_prompt", { label: file.label }, file.content));
97
104
  labels.push(file.label);
98
105
  }
99
106
  }
@@ -101,7 +108,7 @@ export function buildPromptContextAppend(systemPrompt: string | undefined, cwd:
101
108
  if (settings.includeProjectAgentsHook) {
102
109
  const projectAgents = extractHeadingSection(systemPrompt, ["## Project Agents", "## Project Subagents"]);
103
110
  if (projectAgents) {
104
- parts.push(`### before_agent_start: project agents\n\n${projectAgents}`);
111
+ parts.push(xmlBlock("before_agent_start", { source: "project-agents" }, projectAgents));
105
112
  labels.push("project agents hook");
106
113
  }
107
114
  }
@@ -109,7 +116,7 @@ export function buildPromptContextAppend(systemPrompt: string | undefined, cwd:
109
116
  if (settings.includeTaskPanelHook) {
110
117
  const taskReminder = extractBlockByMarkers(systemPrompt, [/^Task workflow reminder:/]);
111
118
  if (taskReminder) {
112
- parts.push(`### before_agent_start: task panel\n\n${taskReminder}`);
119
+ parts.push(xmlBlock("before_agent_start", { source: "task-panel" }, taskReminder));
113
120
  labels.push("task panel hook");
114
121
  }
115
122
  }
@@ -117,7 +124,7 @@ export function buildPromptContextAppend(systemPrompt: string | undefined, cwd:
117
124
  if (settings.includeCavemanHook) {
118
125
  const caveman = extractBlockByMarkers(systemPrompt, [/^You MUST respond in caveman /m]);
119
126
  if (caveman) {
120
- parts.push(`### before_agent_start: caveman\n\n${caveman}`);
127
+ parts.push(xmlBlock("before_agent_start", { source: "caveman" }, caveman));
121
128
  labels.push("caveman hook");
122
129
  }
123
130
  }
@@ -125,10 +132,37 @@ export function buildPromptContextAppend(systemPrompt: string | undefined, cwd:
125
132
  if (parts.length === 0) return { labels };
126
133
  return {
127
134
  labels,
128
- text: [
129
- "## Forwarded Pi Context",
130
- "The following content was explicitly enabled in pi-claude-bridge settings and comes from Pi prompt files or before_agent_start prompt hooks.",
131
- ...parts,
132
- ].join("\n\n"),
135
+ text: xmlBlock(
136
+ "forwarded_pi_context",
137
+ {},
138
+ [
139
+ "The following content was explicitly enabled in pi-claude-bridge settings and comes from Pi prompt files or before_agent_start prompt hooks.",
140
+ ...parts,
141
+ ].join("\n\n"),
142
+ false,
143
+ ),
133
144
  };
134
145
  }
146
+
147
+ function escapeXmlAttr(value: string): string {
148
+ return value
149
+ .replace(/&/g, "&amp;")
150
+ .replace(/"/g, "&quot;")
151
+ .replace(/</g, "&lt;")
152
+ .replace(/>/g, "&gt;");
153
+ }
154
+
155
+ function escapeXmlText(value: string): string {
156
+ return value
157
+ .replace(/&/g, "&amp;")
158
+ .replace(/</g, "&lt;")
159
+ .replace(/>/g, "&gt;");
160
+ }
161
+
162
+ function xmlBlock(tag: string, attrs: Record<string, string>, content: string, escapeContent = true): string {
163
+ const attrText = Object.entries(attrs)
164
+ .map(([key, value]) => ` ${key}="${escapeXmlAttr(value)}"`)
165
+ .join("");
166
+ const body = escapeContent ? escapeXmlText(content.trim()) : content.trim();
167
+ return `<${tag}${attrText}>\n${body}\n</${tag}>`;
168
+ }