@ramarivera/coding-buddy 0.4.0-alpha.6 → 0.4.0-alpha.8
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/adapters/claude/plugin/marketplace.json +2 -2
- package/adapters/claude/plugin/plugin.json +1 -1
- package/adapters/claude/server/index.ts +1 -1
- package/adapters/pi/README.md +19 -0
- package/adapters/pi/events.ts +175 -23
- 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/core/model.ts +6 -0
- package/package.json +3 -2
|
@@ -5,14 +5,14 @@
|
|
|
5
5
|
},
|
|
6
6
|
"metadata": {
|
|
7
7
|
"description": "Permanent coding companion for Claude Code",
|
|
8
|
-
"version": "0.4.0-alpha.
|
|
8
|
+
"version": "0.4.0-alpha.8"
|
|
9
9
|
},
|
|
10
10
|
"plugins": [
|
|
11
11
|
{
|
|
12
12
|
"name": "claude-buddy",
|
|
13
13
|
"source": "./",
|
|
14
14
|
"description": "Permanent coding companion for Claude Code \u2014 survives any update. MCP-based terminal pet with ASCII art, stats, reactions, and personality.",
|
|
15
|
-
"version": "0.4.0-alpha.
|
|
15
|
+
"version": "0.4.0-alpha.8",
|
|
16
16
|
"author": {
|
|
17
17
|
"name": "1270011"
|
|
18
18
|
},
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "claude-buddy",
|
|
3
|
-
"version": "0.4.0-alpha.
|
|
3
|
+
"version": "0.4.0-alpha.8"
|
|
4
4
|
"description": "Permanent coding companion for Claude Code \u2014 survives any update. MCP-based terminal pet with ASCII art, stats, reactions, and personality.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "1270011"
|
package/adapters/pi/README.md
CHANGED
|
@@ -62,3 +62,22 @@ Passive behavior:
|
|
|
62
62
|
Persistence lives under:
|
|
63
63
|
|
|
64
64
|
- `~/.pi/agent/buddy/`
|
|
65
|
+
|
|
66
|
+
## Config
|
|
67
|
+
|
|
68
|
+
Buddy config is stored in:
|
|
69
|
+
|
|
70
|
+
- `~/.pi/agent/buddy/config.json`
|
|
71
|
+
|
|
72
|
+
You can optionally force the end-of-turn buddy comment generator to use a different model than the main pi session:
|
|
73
|
+
|
|
74
|
+
```json
|
|
75
|
+
{
|
|
76
|
+
"turnCommentModel": {
|
|
77
|
+
"provider": "google",
|
|
78
|
+
"model": "gemini-2.5-flash"
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
If `turnCommentModel` is unset, or the configured model cannot be found, buddy falls back to the active session model.
|
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,22 +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
|
-
|
|
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
|
+
});
|
|
93
133
|
deps.ui.refresh(ctx, progress.companion, deps.storage.loadLatest(), progress.achievements);
|
|
94
134
|
return;
|
|
95
135
|
}
|
|
96
136
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
137
|
+
deps.logger.info("turn_end_reaction", {
|
|
138
|
+
source: generated.source,
|
|
139
|
+
reaction: generated.comment,
|
|
140
|
+
});
|
|
102
141
|
|
|
103
|
-
const reaction = deps.service.recordComment(comment, "turn");
|
|
142
|
+
const reaction = deps.service.recordComment(generated.comment, "turn");
|
|
104
143
|
const achievements = mergeAchievements(progress.achievements, reaction.achievements);
|
|
105
144
|
deps.ui.refresh(ctx, reaction.companion, reaction.state, achievements);
|
|
106
145
|
deps.ui.notifyAchievements(ctx, achievements);
|
|
@@ -186,27 +225,61 @@ async function generateTurnComment(
|
|
|
186
225
|
ctx: ExtensionContext,
|
|
187
226
|
companion: Companion,
|
|
188
227
|
event: TurnEndEvent,
|
|
189
|
-
|
|
228
|
+
logger: PiBuddyLogger,
|
|
229
|
+
): Promise<{ comment: string | null; source: "llm" | "fallback" | "none" }> {
|
|
190
230
|
const assistantText = isAssistantMessage(event.message) ? getAssistantText(event.message) : "";
|
|
191
|
-
if (!assistantText.trim())
|
|
231
|
+
if (!assistantText.trim()) {
|
|
232
|
+
logger.warn("turn_comment_skipped", { reason: "empty_assistant_text" });
|
|
233
|
+
return { comment: null, source: "none" };
|
|
234
|
+
}
|
|
192
235
|
|
|
193
|
-
|
|
194
|
-
|
|
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
|
+
});
|
|
195
266
|
if (auth.ok && auth.apiKey) {
|
|
196
267
|
const userMessage: UserMessage = {
|
|
197
268
|
role: "user",
|
|
198
|
-
content: [{ type: "text", text:
|
|
269
|
+
content: [{ type: "text", text: promptText }],
|
|
199
270
|
timestamp: Date.now(),
|
|
200
271
|
};
|
|
201
272
|
|
|
202
273
|
try {
|
|
203
274
|
const response = await complete(
|
|
204
|
-
|
|
205
|
-
{
|
|
275
|
+
turnCommentModel,
|
|
276
|
+
{
|
|
277
|
+
systemPrompt,
|
|
278
|
+
messages: [userMessage],
|
|
279
|
+
},
|
|
206
280
|
{
|
|
207
281
|
apiKey: auth.apiKey,
|
|
208
282
|
headers: auth.headers,
|
|
209
|
-
signal: ctx.signal,
|
|
210
283
|
},
|
|
211
284
|
);
|
|
212
285
|
|
|
@@ -216,15 +289,78 @@ async function generateTurnComment(
|
|
|
216
289
|
.map((block) => block.text)
|
|
217
290
|
.join("\n");
|
|
218
291
|
const normalized = normalizeBuddyComment(text);
|
|
219
|
-
|
|
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
|
+
});
|
|
220
305
|
}
|
|
221
|
-
} catch {
|
|
222
|
-
|
|
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
|
+
});
|
|
223
311
|
}
|
|
312
|
+
} else {
|
|
313
|
+
logger.warn("turn_comment_auth_unavailable", {
|
|
314
|
+
ok: auth.ok,
|
|
315
|
+
message: auth.ok ? "missing api key" : auth.error,
|
|
316
|
+
});
|
|
224
317
|
}
|
|
318
|
+
} else {
|
|
319
|
+
logger.warn("turn_comment_llm_skipped", { reason: "no_model" });
|
|
225
320
|
}
|
|
226
321
|
|
|
227
|
-
|
|
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;
|
|
346
|
+
}
|
|
347
|
+
logger?.warn("turn_comment_model_missing", {
|
|
348
|
+
source: "config",
|
|
349
|
+
provider: activeOverride.provider,
|
|
350
|
+
model: activeOverride.model,
|
|
351
|
+
});
|
|
352
|
+
}
|
|
353
|
+
|
|
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;
|
|
228
364
|
}
|
|
229
365
|
|
|
230
366
|
function getToolResultsText(event: TurnEndEvent): string {
|
|
@@ -238,6 +374,22 @@ function getToolResultsText(event: TurnEndEvent): string {
|
|
|
238
374
|
.slice(0, 4000);
|
|
239
375
|
}
|
|
240
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
|
+
|
|
241
393
|
export function deriveTurnComment(companion: Companion, message: AgentMessage): string | null {
|
|
242
394
|
if (!isAssistantMessage(message)) return null;
|
|
243
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
|
"",
|
package/adapters/pi/storage.ts
CHANGED
package/core/model.ts
CHANGED
|
@@ -26,6 +26,11 @@ export interface ReactionState {
|
|
|
26
26
|
reason: string;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
export interface BuddyTurnCommentModelConfig {
|
|
30
|
+
provider: string;
|
|
31
|
+
model: string;
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
export interface BuddyConfig {
|
|
30
35
|
commentCooldown: number;
|
|
31
36
|
reactionTTL: number;
|
|
@@ -33,6 +38,7 @@ export interface BuddyConfig {
|
|
|
33
38
|
bubblePosition: "top" | "left";
|
|
34
39
|
showRarity: boolean;
|
|
35
40
|
statusLineEnabled: boolean;
|
|
41
|
+
turnCommentModel?: BuddyTurnCommentModelConfig;
|
|
36
42
|
}
|
|
37
43
|
|
|
38
44
|
export interface GlobalCounters {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ramarivera/coding-buddy",
|
|
3
|
-
"version": "0.4.0-alpha.
|
|
3
|
+
"version": "0.4.0-alpha.8",
|
|
4
4
|
"description": "Persistent coding companion for Claude Code and pi",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -55,7 +55,8 @@
|
|
|
55
55
|
},
|
|
56
56
|
"dependencies": {
|
|
57
57
|
"@mariozechner/pi-coding-agent": "0.66.1",
|
|
58
|
-
"@modelcontextprotocol/sdk": "^1.12.1"
|
|
58
|
+
"@modelcontextprotocol/sdk": "^1.12.1",
|
|
59
|
+
"pino": "^10.3.1"
|
|
59
60
|
},
|
|
60
61
|
"devDependencies": {
|
|
61
62
|
"bun-types": "^1.3.11",
|