@ramarivera/coding-buddy 0.4.0-alpha.7 → 0.4.0-alpha.9
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 +18 -39
- package/adapters/claude/hooks/buddy-comment.sh +4 -1
- package/adapters/claude/hooks/name-react.sh +4 -1
- package/adapters/claude/hooks/react.sh +4 -1
- package/adapters/claude/install/backup.ts +36 -118
- package/adapters/claude/install/disable.ts +9 -14
- package/adapters/claude/install/doctor.ts +26 -87
- package/adapters/claude/install/install.ts +39 -66
- package/adapters/claude/install/test-statusline.ts +8 -18
- package/adapters/claude/install/uninstall.ts +18 -26
- package/adapters/claude/plugin/marketplace.json +4 -4
- package/adapters/claude/plugin/plugin.json +3 -5
- package/adapters/claude/server/index.ts +132 -5
- package/adapters/claude/server/path.ts +12 -0
- package/adapters/claude/skills/buddy/SKILL.md +16 -1
- package/adapters/claude/statusline/buddy-status.sh +22 -3
- package/adapters/claude/storage/paths.ts +9 -0
- package/adapters/claude/storage/settings.ts +53 -3
- package/adapters/claude/storage/state.ts +22 -4
- package/adapters/pi/README.md +19 -0
- package/adapters/pi/events.ts +176 -19
- package/adapters/pi/index.ts +3 -1
- package/adapters/pi/logger.ts +52 -0
- package/adapters/pi/prompt.ts +18 -0
- package/adapters/pi/storage.ts +1 -0
- package/cli/biomes.ts +309 -0
- package/cli/buddy-shell.ts +818 -0
- package/cli/index.ts +7 -0
- package/cli/tui.tsx +2244 -0
- package/cli/upgrade.ts +213 -0
- package/core/model.ts +6 -0
- package/package.json +78 -62
- package/scripts/paths.sh +40 -0
- package/server/achievements.ts +15 -0
- package/server/art.ts +1 -0
- package/server/engine.ts +1 -0
- package/server/mcp-launcher.sh +16 -0
- package/server/path.ts +30 -0
- package/server/reactions.ts +1 -0
- package/server/state.ts +3 -0
- package/adapters/claude/popup/buddy-popup.sh +0 -92
- package/adapters/claude/popup/buddy-render.sh +0 -540
- package/adapters/claude/popup/popup-manager.sh +0 -355
package/adapters/pi/events.ts
CHANGED
|
@@ -12,21 +12,28 @@ import type { AgentMessage } from "@mariozechner/pi-agent-core";
|
|
|
12
12
|
import { complete, type AssistantMessage, type TextContent, type UserMessage } from "@mariozechner/pi-ai";
|
|
13
13
|
import { BuddyCommandService } from "../../core/command-service.ts";
|
|
14
14
|
import type { Achievement } from "../../core/achievements.ts";
|
|
15
|
-
import type { Companion } from "../../core/model.ts";
|
|
15
|
+
import type { BuddyTurnCommentModelConfig, Companion } from "../../core/model.ts";
|
|
16
16
|
import { getNameReaction, getSuccessReaction } from "../../core/reactions.ts";
|
|
17
17
|
import { PiBuddyStorage } from "./storage.ts";
|
|
18
|
-
import { buildBuddyReactionPrompt, normalizeBuddyComment, stripBuddyComments } from "./prompt.ts";
|
|
18
|
+
import { buildBuddyReactionPrompt, buildBuddyReactionSystemPrompt, normalizeBuddyComment, stripBuddyComments } from "./prompt.ts";
|
|
19
19
|
import { PiBuddyUI } from "./ui.ts";
|
|
20
|
+
import { PiBuddyLogger } from "./logger.ts";
|
|
20
21
|
|
|
21
22
|
interface RegisterBuddyEventsDeps {
|
|
22
23
|
service: BuddyCommandService;
|
|
23
24
|
storage: PiBuddyStorage;
|
|
24
25
|
ui: PiBuddyUI;
|
|
26
|
+
logger: PiBuddyLogger;
|
|
25
27
|
}
|
|
26
28
|
|
|
27
29
|
export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsDeps): void {
|
|
28
30
|
pi.on("session_start", async (_event: SessionStartEvent, ctx: ExtensionContext) => {
|
|
29
31
|
const result = deps.service.ensureCompanion();
|
|
32
|
+
deps.logger.info("session_start", {
|
|
33
|
+
companion: result.companion.name,
|
|
34
|
+
species: result.companion.bones.species,
|
|
35
|
+
created: result.created,
|
|
36
|
+
});
|
|
30
37
|
deps.ui.refresh(ctx, result.companion, deps.storage.loadLatest(), result.achievements);
|
|
31
38
|
if (result.created) {
|
|
32
39
|
ctx.ui.notify(`A new buddy hatched: ${result.companion.name}`, "info");
|
|
@@ -36,6 +43,10 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
|
|
|
36
43
|
|
|
37
44
|
pi.on("input", async (event: InputEvent, ctx: ExtensionContext): Promise<InputEventResult> => {
|
|
38
45
|
if (event.source === "extension" || deps.storage.isMuted()) {
|
|
46
|
+
deps.logger.debug("input_skipped", {
|
|
47
|
+
source: event.source,
|
|
48
|
+
muted: deps.storage.isMuted(),
|
|
49
|
+
});
|
|
39
50
|
return { action: "continue" };
|
|
40
51
|
}
|
|
41
52
|
|
|
@@ -45,6 +56,12 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
|
|
|
45
56
|
return { action: "continue" };
|
|
46
57
|
}
|
|
47
58
|
|
|
59
|
+
deps.logger.info("name_mention_detected", {
|
|
60
|
+
companion: companion.name,
|
|
61
|
+
species: companion.bones.species,
|
|
62
|
+
textPreview: event.text.slice(0, 160),
|
|
63
|
+
});
|
|
64
|
+
|
|
48
65
|
const reaction = getNameReaction(companion.bones.species);
|
|
49
66
|
const result = deps.service.recordComment(reaction, "turn");
|
|
50
67
|
deps.ui.refresh(ctx, result.companion, result.state, result.achievements);
|
|
@@ -53,7 +70,14 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
|
|
|
53
70
|
});
|
|
54
71
|
|
|
55
72
|
pi.on("tool_result", async (event: ToolResultEvent, ctx: ExtensionContext) => {
|
|
56
|
-
if (deps.storage.isMuted()
|
|
73
|
+
if (deps.storage.isMuted()) {
|
|
74
|
+
deps.logger.debug("tool_result_skipped", { reason: "muted" });
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
if (!shouldEmitPassiveReaction(deps.storage)) {
|
|
78
|
+
deps.logger.debug("tool_result_skipped", { reason: "cooldown" });
|
|
79
|
+
return;
|
|
80
|
+
}
|
|
57
81
|
|
|
58
82
|
const text = extractToolText(event);
|
|
59
83
|
let result:
|
|
@@ -77,7 +101,18 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
|
|
|
77
101
|
}
|
|
78
102
|
}
|
|
79
103
|
|
|
80
|
-
if (!result)
|
|
104
|
+
if (!result) {
|
|
105
|
+
deps.logger.debug("tool_result_ignored", {
|
|
106
|
+
isError: event.isError,
|
|
107
|
+
textPreview: text.slice(0, 200),
|
|
108
|
+
});
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
deps.logger.info("tool_result_reaction", {
|
|
112
|
+
reason: result.state.reason,
|
|
113
|
+
reaction: result.state.reaction,
|
|
114
|
+
textPreview: text.slice(0, 200),
|
|
115
|
+
});
|
|
81
116
|
deps.ui.refresh(ctx, result.companion, result.state, result.achievements);
|
|
82
117
|
deps.ui.notifyAchievements(ctx, result.achievements);
|
|
83
118
|
});
|
|
@@ -85,17 +120,26 @@ export function registerBuddyEvents(pi: ExtensionAPI, deps: RegisterBuddyEventsD
|
|
|
85
120
|
pi.on("turn_end", async (event: TurnEndEvent, ctx: ExtensionContext) => {
|
|
86
121
|
const progress = deps.service.recordTurnOnly();
|
|
87
122
|
if (deps.storage.isMuted()) {
|
|
123
|
+
deps.logger.debug("turn_end_skipped", { reason: "muted" });
|
|
88
124
|
deps.ui.refresh(ctx, progress.companion, null, progress.achievements);
|
|
89
125
|
return;
|
|
90
126
|
}
|
|
91
127
|
|
|
92
|
-
const
|
|
93
|
-
if (!comment) {
|
|
128
|
+
const generated = await generateTurnComment(ctx, progress.companion, event, deps.logger);
|
|
129
|
+
if (!generated.comment) {
|
|
130
|
+
deps.logger.warn("turn_end_comment_missing", {
|
|
131
|
+
assistantPreview: isAssistantMessage(event.message) ? getAssistantText(event.message).slice(0, 200) : "",
|
|
132
|
+
});
|
|
94
133
|
deps.ui.refresh(ctx, progress.companion, deps.storage.loadLatest(), progress.achievements);
|
|
95
134
|
return;
|
|
96
135
|
}
|
|
97
136
|
|
|
98
|
-
|
|
137
|
+
deps.logger.info("turn_end_reaction", {
|
|
138
|
+
source: generated.source,
|
|
139
|
+
reaction: generated.comment,
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
const reaction = deps.service.recordComment(generated.comment, "turn");
|
|
99
143
|
const achievements = mergeAchievements(progress.achievements, reaction.achievements);
|
|
100
144
|
deps.ui.refresh(ctx, reaction.companion, reaction.state, achievements);
|
|
101
145
|
deps.ui.notifyAchievements(ctx, achievements);
|
|
@@ -181,27 +225,61 @@ async function generateTurnComment(
|
|
|
181
225
|
ctx: ExtensionContext,
|
|
182
226
|
companion: Companion,
|
|
183
227
|
event: TurnEndEvent,
|
|
184
|
-
|
|
228
|
+
logger: PiBuddyLogger,
|
|
229
|
+
): Promise<{ comment: string | null; source: "llm" | "fallback" | "none" }> {
|
|
185
230
|
const assistantText = isAssistantMessage(event.message) ? getAssistantText(event.message) : "";
|
|
186
|
-
if (!assistantText.trim())
|
|
231
|
+
if (!assistantText.trim()) {
|
|
232
|
+
logger.warn("turn_comment_skipped", { reason: "empty_assistant_text" });
|
|
233
|
+
return { comment: null, source: "none" };
|
|
234
|
+
}
|
|
187
235
|
|
|
188
|
-
|
|
189
|
-
|
|
236
|
+
const turnCommentModel = resolveTurnCommentModel(ctx, logger);
|
|
237
|
+
if (turnCommentModel) {
|
|
238
|
+
const toolResultsText = getToolResultsText(event);
|
|
239
|
+
const userText = getUserPromptText(ctx);
|
|
240
|
+
const systemPrompt = buildBuddyReactionSystemPrompt(companion);
|
|
241
|
+
const promptText = buildBuddyReactionPrompt(companion, assistantText, toolResultsText, userText);
|
|
242
|
+
logger.info("turn_comment_llm_attempt", {
|
|
243
|
+
modelProvider: turnCommentModel.provider,
|
|
244
|
+
modelId: turnCommentModel.id,
|
|
245
|
+
assistantPreview: assistantText.slice(0, 200),
|
|
246
|
+
toolPreview: toolResultsText.slice(0, 200),
|
|
247
|
+
assistantLength: assistantText.length,
|
|
248
|
+
toolLength: toolResultsText.length,
|
|
249
|
+
promptLength: promptText.length,
|
|
250
|
+
systemPromptLength: systemPrompt.length,
|
|
251
|
+
toolResultCount: event.toolResults.length,
|
|
252
|
+
});
|
|
253
|
+
logger.debug("turn_comment_llm_prompt", {
|
|
254
|
+
systemPromptPreview: systemPrompt.slice(0, 800),
|
|
255
|
+
promptPreview: promptText.slice(0, 1200),
|
|
256
|
+
userText,
|
|
257
|
+
assistantText,
|
|
258
|
+
toolResultsText,
|
|
259
|
+
});
|
|
260
|
+
const auth = await ctx.modelRegistry.getApiKeyAndHeaders(turnCommentModel);
|
|
261
|
+
logger.debug("turn_comment_auth", {
|
|
262
|
+
ok: auth.ok,
|
|
263
|
+
hasApiKey: auth.ok ? !!auth.apiKey : false,
|
|
264
|
+
headerKeys: auth.ok ? Object.keys(auth.headers ?? {}) : [],
|
|
265
|
+
});
|
|
190
266
|
if (auth.ok && auth.apiKey) {
|
|
191
267
|
const userMessage: UserMessage = {
|
|
192
268
|
role: "user",
|
|
193
|
-
content: [{ type: "text", text:
|
|
269
|
+
content: [{ type: "text", text: promptText }],
|
|
194
270
|
timestamp: Date.now(),
|
|
195
271
|
};
|
|
196
272
|
|
|
197
273
|
try {
|
|
198
274
|
const response = await complete(
|
|
199
|
-
|
|
200
|
-
{
|
|
275
|
+
turnCommentModel,
|
|
276
|
+
{
|
|
277
|
+
systemPrompt,
|
|
278
|
+
messages: [userMessage],
|
|
279
|
+
},
|
|
201
280
|
{
|
|
202
281
|
apiKey: auth.apiKey,
|
|
203
282
|
headers: auth.headers,
|
|
204
|
-
signal: ctx.signal,
|
|
205
283
|
},
|
|
206
284
|
);
|
|
207
285
|
|
|
@@ -211,15 +289,78 @@ async function generateTurnComment(
|
|
|
211
289
|
.map((block) => block.text)
|
|
212
290
|
.join("\n");
|
|
213
291
|
const normalized = normalizeBuddyComment(text);
|
|
214
|
-
|
|
292
|
+
logger.info("turn_comment_llm_result", {
|
|
293
|
+
stopReason: response.stopReason,
|
|
294
|
+
errorMessage: "errorMessage" in response ? (response as { errorMessage?: string }).errorMessage : undefined,
|
|
295
|
+
contentTypes: response.content.map((block) => block.type),
|
|
296
|
+
contentCount: response.content.length,
|
|
297
|
+
rawPreview: text.slice(0, 200),
|
|
298
|
+
rawLength: text.length,
|
|
299
|
+
normalized,
|
|
300
|
+
});
|
|
301
|
+
if (normalized) return { comment: normalized, source: "llm" };
|
|
302
|
+
logger.warn("turn_comment_llm_empty", {
|
|
303
|
+
rawPreview: text.slice(0, 200),
|
|
304
|
+
});
|
|
215
305
|
}
|
|
216
|
-
} catch {
|
|
217
|
-
|
|
306
|
+
} catch (error) {
|
|
307
|
+
logger.error("turn_comment_llm_error", {
|
|
308
|
+
message: error instanceof Error ? error.message : String(error),
|
|
309
|
+
stack: error instanceof Error ? error.stack : undefined,
|
|
310
|
+
});
|
|
218
311
|
}
|
|
312
|
+
} else {
|
|
313
|
+
logger.warn("turn_comment_auth_unavailable", {
|
|
314
|
+
ok: auth.ok,
|
|
315
|
+
message: auth.ok ? "missing api key" : auth.error,
|
|
316
|
+
});
|
|
317
|
+
}
|
|
318
|
+
} else {
|
|
319
|
+
logger.warn("turn_comment_llm_skipped", { reason: "no_model" });
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
const fallback = deriveTurnComment(companion, event.message);
|
|
323
|
+
logger.warn("turn_comment_fallback", {
|
|
324
|
+
fallback,
|
|
325
|
+
assistantPreview: assistantText.slice(0, 200),
|
|
326
|
+
});
|
|
327
|
+
return { comment: fallback, source: fallback ? "fallback" : "none" };
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
export function resolveTurnCommentModel(
|
|
331
|
+
ctx: ExtensionContext,
|
|
332
|
+
logger?: PiBuddyLogger,
|
|
333
|
+
override?: BuddyTurnCommentModelConfig,
|
|
334
|
+
): NonNullable<ExtensionContext["model"]> | null {
|
|
335
|
+
const configuredOverride = override ?? new PiBuddyStorage().loadPiConfig().turnCommentModel;
|
|
336
|
+
const activeOverride = configuredOverride;
|
|
337
|
+
if (activeOverride?.provider && activeOverride.model) {
|
|
338
|
+
const model = ctx.modelRegistry.find(activeOverride.provider, activeOverride.model);
|
|
339
|
+
if (model) {
|
|
340
|
+
logger?.debug("turn_comment_model_selected", {
|
|
341
|
+
source: "config",
|
|
342
|
+
provider: model.provider,
|
|
343
|
+
model: model.id,
|
|
344
|
+
});
|
|
345
|
+
return model;
|
|
219
346
|
}
|
|
347
|
+
logger?.warn("turn_comment_model_missing", {
|
|
348
|
+
source: "config",
|
|
349
|
+
provider: activeOverride.provider,
|
|
350
|
+
model: activeOverride.model,
|
|
351
|
+
});
|
|
220
352
|
}
|
|
221
353
|
|
|
222
|
-
|
|
354
|
+
if (ctx.model) {
|
|
355
|
+
logger?.debug("turn_comment_model_selected", {
|
|
356
|
+
source: "session",
|
|
357
|
+
provider: ctx.model.provider,
|
|
358
|
+
model: ctx.model.id,
|
|
359
|
+
});
|
|
360
|
+
return ctx.model;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
return null;
|
|
223
364
|
}
|
|
224
365
|
|
|
225
366
|
function getToolResultsText(event: TurnEndEvent): string {
|
|
@@ -233,6 +374,22 @@ function getToolResultsText(event: TurnEndEvent): string {
|
|
|
233
374
|
.slice(0, 4000);
|
|
234
375
|
}
|
|
235
376
|
|
|
377
|
+
function getUserPromptText(ctx: ExtensionContext): string {
|
|
378
|
+
const branch = ctx.sessionManager.getBranch();
|
|
379
|
+
for (let i = branch.length - 1; i >= 0; i--) {
|
|
380
|
+
const entry = branch[i];
|
|
381
|
+
if (entry.type !== "message") continue;
|
|
382
|
+
const message = entry.message;
|
|
383
|
+
if (message.role !== "user" || !Array.isArray(message.content)) continue;
|
|
384
|
+
return message.content
|
|
385
|
+
.filter((block): block is TextContent => block.type === "text")
|
|
386
|
+
.map((block) => block.text)
|
|
387
|
+
.join("\n")
|
|
388
|
+
.slice(0, 4000);
|
|
389
|
+
}
|
|
390
|
+
return "";
|
|
391
|
+
}
|
|
392
|
+
|
|
236
393
|
export function deriveTurnComment(companion: Companion, message: AgentMessage): string | null {
|
|
237
394
|
if (!isAssistantMessage(message)) return null;
|
|
238
395
|
|
package/adapters/pi/index.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { ExtensionAPI } from "@mariozechner/pi-coding-agent";
|
|
2
2
|
import { BuddyCommandService } from "../../core/command-service.ts";
|
|
3
3
|
import { PiIdentityProvider } from "./identity.ts";
|
|
4
|
+
import { PiBuddyLogger } from "./logger.ts";
|
|
4
5
|
import { registerBuddyCommands } from "./commands.ts";
|
|
5
6
|
import { registerBuddyEvents } from "./events.ts";
|
|
6
7
|
import { PiBuddyStorage } from "./storage.ts";
|
|
@@ -10,6 +11,7 @@ import { PiBuddyUI } from "./ui.ts";
|
|
|
10
11
|
export default function registerPiBuddyExtension(pi: ExtensionAPI): void {
|
|
11
12
|
const storage = new PiBuddyStorage();
|
|
12
13
|
const identity = new PiIdentityProvider(storage);
|
|
14
|
+
const logger = new PiBuddyLogger(storage);
|
|
13
15
|
const service = new BuddyCommandService({
|
|
14
16
|
identity,
|
|
15
17
|
buddies: storage,
|
|
@@ -20,6 +22,6 @@ export default function registerPiBuddyExtension(pi: ExtensionAPI): void {
|
|
|
20
22
|
const ui = new PiBuddyUI(storage);
|
|
21
23
|
|
|
22
24
|
registerBuddyCommands(pi, { service, storage, ui });
|
|
23
|
-
registerBuddyEvents(pi, { service, storage, ui });
|
|
25
|
+
registerBuddyEvents(pi, { service, storage, ui, logger });
|
|
24
26
|
registerBuddyTools(pi);
|
|
25
27
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { mkdirSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import pino, { type Logger } from "pino";
|
|
4
|
+
import { PiBuddyStorage } from "./storage.ts";
|
|
5
|
+
|
|
6
|
+
function resolveLogLevel(): pino.LevelWithSilent {
|
|
7
|
+
const level = (process.env.PI_BUDDY_LOG_LEVEL
|
|
8
|
+
?? process.env.CODING_BUDDY_LOG_LEVEL
|
|
9
|
+
?? "info").toLowerCase();
|
|
10
|
+
|
|
11
|
+
if (["fatal", "error", "warn", "info", "debug", "trace", "silent"].includes(level)) {
|
|
12
|
+
return level as pino.LevelWithSilent;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return "info";
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export class PiBuddyLogger {
|
|
19
|
+
readonly filePath: string;
|
|
20
|
+
readonly logger: Logger;
|
|
21
|
+
|
|
22
|
+
constructor(storage: PiBuddyStorage) {
|
|
23
|
+
const logDir = join(storage.stateDir, "logs");
|
|
24
|
+
mkdirSync(logDir, { recursive: true });
|
|
25
|
+
this.filePath = join(logDir, "buddy.log");
|
|
26
|
+
this.logger = pino(
|
|
27
|
+
{
|
|
28
|
+
name: "pi-buddy",
|
|
29
|
+
level: resolveLogLevel(),
|
|
30
|
+
base: undefined,
|
|
31
|
+
timestamp: pino.stdTimeFunctions.isoTime,
|
|
32
|
+
},
|
|
33
|
+
pino.destination({ dest: this.filePath, append: true, mkdir: true, sync: true }),
|
|
34
|
+
);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
info(event: string, data: Record<string, unknown> = {}): void {
|
|
38
|
+
this.logger.info({ event, ...data });
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
warn(event: string, data: Record<string, unknown> = {}): void {
|
|
42
|
+
this.logger.warn({ event, ...data });
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
error(event: string, data: Record<string, unknown> = {}): void {
|
|
46
|
+
this.logger.error({ event, ...data });
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
debug(event: string, data: Record<string, unknown> = {}): void {
|
|
50
|
+
this.logger.debug({ event, ...data });
|
|
51
|
+
}
|
|
52
|
+
}
|
package/adapters/pi/prompt.ts
CHANGED
|
@@ -1,9 +1,24 @@
|
|
|
1
1
|
import type { Companion } from "../../core/model.ts";
|
|
2
2
|
|
|
3
|
+
export function buildBuddyReactionSystemPrompt(companion: Companion): string {
|
|
4
|
+
return [
|
|
5
|
+
`A small ${companion.bones.rarity} ${companion.bones.species} named ${companion.name} watches from the status line. You are not ${companion.name} — it is a separate creature.`,
|
|
6
|
+
`Personality: ${companion.personality}`,
|
|
7
|
+
`Peak stat: ${companion.bones.peak} (${companion.bones.stats[companion.bones.peak]}). Dump stat: ${companion.bones.dump} (${companion.bones.stats[companion.bones.dump]}).`,
|
|
8
|
+
"",
|
|
9
|
+
`Write as ${companion.name} (a ${companion.bones.species}), not as the assistant.`,
|
|
10
|
+
"Reference something SPECIFIC from this turn — a pitfall, compliment, warning, pattern, file, test, or edge case.",
|
|
11
|
+
"Return exactly one short sentence, max 150 chars.",
|
|
12
|
+
"Use *asterisks* for physical actions when useful.",
|
|
13
|
+
"Do not explain yourself. Do not use markdown fences. Output only the buddy reaction line.",
|
|
14
|
+
].join("\n");
|
|
15
|
+
}
|
|
16
|
+
|
|
3
17
|
export function buildBuddyReactionPrompt(
|
|
4
18
|
companion: Companion,
|
|
5
19
|
assistantText: string,
|
|
6
20
|
toolText: string,
|
|
21
|
+
userText: string,
|
|
7
22
|
): string {
|
|
8
23
|
return [
|
|
9
24
|
`You are generating a single end-of-turn buddy reaction for ${companion.name}, a ${companion.bones.rarity} ${companion.bones.species}.`,
|
|
@@ -18,6 +33,9 @@ export function buildBuddyReactionPrompt(
|
|
|
18
33
|
"- Use *asterisks* for physical actions when useful.",
|
|
19
34
|
"- Output only the reaction line, with no quotes, labels, markdown fences, or explanation.",
|
|
20
35
|
"",
|
|
36
|
+
"User prompt:",
|
|
37
|
+
userText || "(unknown)",
|
|
38
|
+
"",
|
|
21
39
|
"Assistant response:",
|
|
22
40
|
assistantText || "(empty)",
|
|
23
41
|
"",
|