portable-agent-layer 0.23.0 → 0.23.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.
|
@@ -216,7 +216,26 @@ Only add threads that genuinely need follow-up. Resolve existing threads if this
|
|
|
216
216
|
bun ~/.pal/tools/thread.ts --resolve --id <id>
|
|
217
217
|
```
|
|
218
218
|
|
|
219
|
-
**4.
|
|
219
|
+
**4. Opinion capture** — scan the conversation for moments where the user:
|
|
220
|
+
- Confirmed something you did: "yes exactly", "keep doing that", "10 rated", accepted without pushback
|
|
221
|
+
- Corrected something you did: "no", "don't do that", "stop", "that's not what I meant"
|
|
222
|
+
- Revealed a preference by repeating a pattern (asked for concise answers twice, always checked PAI first, etc.)
|
|
223
|
+
|
|
224
|
+
For each, invoke the opinion tool:
|
|
225
|
+
```bash
|
|
226
|
+
# User confirmed a preference
|
|
227
|
+
bun ~/.pal/skills/opinion/tools/opinion.ts evidence "matching keywords" --confirmation "what they confirmed"
|
|
228
|
+
|
|
229
|
+
# User corrected a preference
|
|
230
|
+
bun ~/.pal/skills/opinion/tools/opinion.ts evidence "matching keywords" --contradiction "what they corrected"
|
|
231
|
+
|
|
232
|
+
# New pattern observed (no existing opinion matches)
|
|
233
|
+
bun ~/.pal/skills/opinion/tools/opinion.ts add "the preference" --category communication|technical|workflow|general
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Skip if nothing in the conversation touched preferences or working style.
|
|
237
|
+
|
|
238
|
+
**5. Wisdom Frame** (Extended+ only) — if the session produced a genuine, reusable insight:
|
|
220
239
|
|
|
221
240
|
```bash
|
|
222
241
|
bun ~/.pal/tools/wisdom-frame.ts --domain <domain> --observation "insight" [--type principle|contextual-rule|anti-pattern|evolution]
|
package/package.json
CHANGED
|
@@ -30,7 +30,8 @@ export async function captureFailure(
|
|
|
30
30
|
rating: number,
|
|
31
31
|
context: string,
|
|
32
32
|
transcript: string,
|
|
33
|
-
detailedContext?: string
|
|
33
|
+
detailedContext?: string,
|
|
34
|
+
principle?: string
|
|
34
35
|
): Promise<void> {
|
|
35
36
|
const messages = parseMessages(transcript);
|
|
36
37
|
|
|
@@ -55,6 +56,7 @@ export async function captureFailure(
|
|
|
55
56
|
ts: new Date().toISOString(),
|
|
56
57
|
slug,
|
|
57
58
|
};
|
|
59
|
+
if (principle) meta.principle = principle;
|
|
58
60
|
|
|
59
61
|
const body = [
|
|
60
62
|
"## What Happened",
|
|
@@ -166,8 +166,16 @@ const SENTIMENT_SCHEMA = {
|
|
|
166
166
|
confidence: { type: "number" },
|
|
167
167
|
summary: { type: "string" },
|
|
168
168
|
detailed_context: { type: "string" },
|
|
169
|
+
principle: { type: "string" },
|
|
169
170
|
},
|
|
170
|
-
required: [
|
|
171
|
+
required: [
|
|
172
|
+
"rating",
|
|
173
|
+
"sentiment",
|
|
174
|
+
"confidence",
|
|
175
|
+
"summary",
|
|
176
|
+
"detailed_context",
|
|
177
|
+
"principle",
|
|
178
|
+
],
|
|
171
179
|
additionalProperties: false,
|
|
172
180
|
} as const;
|
|
173
181
|
|
|
@@ -177,6 +185,7 @@ interface SentimentResult {
|
|
|
177
185
|
confidence: number;
|
|
178
186
|
summary: string;
|
|
179
187
|
detailed_context: string;
|
|
188
|
+
principle: string;
|
|
180
189
|
}
|
|
181
190
|
|
|
182
191
|
const SENTIMENT_SYSTEM_PROMPT = `Analyze the user's message for emotional sentiment toward the AI assistant.
|
|
@@ -187,7 +196,8 @@ OUTPUT FORMAT (JSON only):
|
|
|
187
196
|
"sentiment": "positive" | "negative" | "neutral",
|
|
188
197
|
"confidence": <0.0-1.0>,
|
|
189
198
|
"summary": "<brief explanation, 10 words max>",
|
|
190
|
-
"detailed_context": "<comprehensive analysis, 50-150 words>"
|
|
199
|
+
"detailed_context": "<comprehensive analysis, 50-150 words>",
|
|
200
|
+
"principle": "<one actionable rule the AI should follow to avoid this failure or repeat this success, 10-20 words. Start with a verb: 'Verify...', 'Always...', 'Never...', 'Ask before...'>"
|
|
191
201
|
}
|
|
192
202
|
|
|
193
203
|
DETAILED_CONTEXT REQUIREMENTS:
|
|
@@ -240,6 +250,7 @@ function handleRating(
|
|
|
240
250
|
context: string,
|
|
241
251
|
source: string,
|
|
242
252
|
detailedContext?: string,
|
|
253
|
+
principle?: string,
|
|
243
254
|
sessionId?: string,
|
|
244
255
|
userMessage?: string
|
|
245
256
|
): void {
|
|
@@ -257,6 +268,7 @@ function handleRating(
|
|
|
257
268
|
context,
|
|
258
269
|
source,
|
|
259
270
|
detailedContext,
|
|
271
|
+
principle,
|
|
260
272
|
responsePreview,
|
|
261
273
|
userPreview,
|
|
262
274
|
ts: now(),
|
|
@@ -284,6 +296,7 @@ async function handleImplicitSentiment(
|
|
|
284
296
|
`Direct praise: "${trimmed}"`,
|
|
285
297
|
"implicit",
|
|
286
298
|
undefined,
|
|
299
|
+
undefined,
|
|
287
300
|
sessionId,
|
|
288
301
|
trimmed
|
|
289
302
|
);
|
|
@@ -328,6 +341,7 @@ async function handleImplicitSentiment(
|
|
|
328
341
|
`${parsed.summary}: ${trimmed.slice(0, 200)}`,
|
|
329
342
|
"implicit",
|
|
330
343
|
parsed.detailed_context,
|
|
344
|
+
parsed.principle,
|
|
331
345
|
sessionId,
|
|
332
346
|
trimmed
|
|
333
347
|
);
|
|
@@ -352,6 +366,7 @@ export async function captureRating(message: string, sessionId?: string): Promis
|
|
|
352
366
|
explicit.comment || cleaned.slice(0, 200),
|
|
353
367
|
"explicit",
|
|
354
368
|
undefined,
|
|
369
|
+
undefined,
|
|
355
370
|
sessionId,
|
|
356
371
|
cleaned
|
|
357
372
|
);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Auto-trigger for relationship reflect — runs when conditions are met:
|
|
3
|
-
* -
|
|
4
|
-
* -
|
|
3
|
+
* - 1+ days since last reflect
|
|
4
|
+
* - 5+ new relationship notes since last reflect
|
|
5
5
|
*
|
|
6
6
|
* Spawns `bun run tool:reflect` as a detached background process.
|
|
7
7
|
*/
|
|
@@ -12,8 +12,8 @@ import { logDebug } from "../lib/log";
|
|
|
12
12
|
import { getLastReflectDate } from "../lib/opinions";
|
|
13
13
|
import { palPkg, paths } from "../lib/paths";
|
|
14
14
|
|
|
15
|
-
const MIN_DAYS_BETWEEN =
|
|
16
|
-
const MIN_NEW_NOTES =
|
|
15
|
+
const MIN_DAYS_BETWEEN = 1;
|
|
16
|
+
const MIN_NEW_NOTES = 5;
|
|
17
17
|
|
|
18
18
|
function countNotesSince(since: string): number {
|
|
19
19
|
const relDir = paths.relationship();
|
package/src/hooks/lib/context.ts
CHANGED
|
@@ -231,10 +231,11 @@ export function loadFailurePatterns(): string {
|
|
|
231
231
|
|
|
232
232
|
const lines = entries.map((e) => {
|
|
233
233
|
const label = e.rating ? `[${e.rating}/10]` : "";
|
|
234
|
-
|
|
234
|
+
const text = e.principle || e.context;
|
|
235
|
+
return `- ${label} ${text}`.trim();
|
|
235
236
|
});
|
|
236
237
|
|
|
237
|
-
return ["## Recent
|
|
238
|
+
return ["## Lessons from Recent Failures — Apply These Now", ...lines].join("\n");
|
|
238
239
|
} catch {
|
|
239
240
|
return "";
|
|
240
241
|
}
|
package/src/hooks/lib/stop.ts
CHANGED
|
@@ -13,6 +13,7 @@ import { runSynthesis } from "../handlers/synthesis";
|
|
|
13
13
|
import { resetTab } from "../handlers/tab";
|
|
14
14
|
import { updateCounts } from "../handlers/update-counts";
|
|
15
15
|
import { captureWorkSession } from "../handlers/work-session";
|
|
16
|
+
import { inference } from "./inference";
|
|
16
17
|
import { logDebug, logError } from "./log";
|
|
17
18
|
import { ensureDir, paths } from "./paths";
|
|
18
19
|
import { extractContent, extractLastAssistant, parseMessages } from "./transcript";
|
|
@@ -137,15 +138,63 @@ async function checkPendingFailure(transcript: string): Promise<void> {
|
|
|
137
138
|
rating: number;
|
|
138
139
|
context: string;
|
|
139
140
|
detailedContext?: string;
|
|
141
|
+
principle?: string;
|
|
140
142
|
responsePreview?: string;
|
|
141
143
|
userPreview?: string;
|
|
142
144
|
};
|
|
143
145
|
unlinkSync(pendingPath);
|
|
146
|
+
|
|
147
|
+
// Extract principle from full transcript if not already present
|
|
148
|
+
let { principle, detailedContext } = pending;
|
|
149
|
+
if (!principle) {
|
|
150
|
+
try {
|
|
151
|
+
const msgs = parseMessages(transcript);
|
|
152
|
+
const recent = msgs
|
|
153
|
+
.slice(-10)
|
|
154
|
+
.map((m) => `${m.role.toUpperCase()}: ${extractContent(m).slice(0, 300)}`)
|
|
155
|
+
.join("\n\n");
|
|
156
|
+
|
|
157
|
+
const result = await inference({
|
|
158
|
+
system: `Analyze this failed AI interaction. The user rated it ${pending.rating}/10.
|
|
159
|
+
|
|
160
|
+
Return JSON:
|
|
161
|
+
{
|
|
162
|
+
"principle": "<one actionable rule the AI should follow, 10-20 words. Start with a verb: 'Verify...', 'Always...', 'Never...', 'Ask before...'>",
|
|
163
|
+
"detailed_context": "<what went wrong and why, 50-150 words>"
|
|
164
|
+
}`,
|
|
165
|
+
user: `User feedback: ${pending.context}\n\nConversation:\n${recent}`,
|
|
166
|
+
maxTokens: 400,
|
|
167
|
+
timeout: 10000,
|
|
168
|
+
jsonSchema: {
|
|
169
|
+
type: "object" as const,
|
|
170
|
+
properties: {
|
|
171
|
+
principle: { type: "string" as const },
|
|
172
|
+
detailed_context: { type: "string" as const },
|
|
173
|
+
},
|
|
174
|
+
required: ["principle", "detailed_context"],
|
|
175
|
+
additionalProperties: false,
|
|
176
|
+
},
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
if (result.success && result.output) {
|
|
180
|
+
const parsed = JSON.parse(result.output) as {
|
|
181
|
+
principle?: string;
|
|
182
|
+
detailed_context?: string;
|
|
183
|
+
};
|
|
184
|
+
principle = parsed.principle || undefined;
|
|
185
|
+
if (!detailedContext) detailedContext = parsed.detailed_context || undefined;
|
|
186
|
+
}
|
|
187
|
+
} catch {
|
|
188
|
+
/* graceful fallback — capture without principle */
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
144
192
|
await captureFailure(
|
|
145
193
|
pending.rating,
|
|
146
194
|
pending.context,
|
|
147
195
|
transcript,
|
|
148
|
-
|
|
196
|
+
detailedContext,
|
|
197
|
+
principle
|
|
149
198
|
);
|
|
150
199
|
} catch {
|
|
151
200
|
// Non-critical
|