portable-agent-layer 0.51.2 → 0.53.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.
- package/README.md +1 -0
- package/assets/skills/analyze-pdf/SKILL.md +12 -0
- package/assets/skills/analyze-pdf/tools/pdf-read.ts +35 -0
- package/assets/skills/pal-analyze/SKILL.md +54 -0
- package/assets/skills/pal-reflect/SKILL.md +52 -0
- package/assets/statusline.ps1 +2 -2
- package/assets/statusline.sh +2 -2
- package/assets/templates/PAL/ALGORITHM.md +35 -6
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +2 -3
- package/assets/templates/pal-settings.json +0 -3
- package/package.json +7 -4
- package/src/cli/index.ts +6 -0
- package/src/hooks/LoadContext.ts +2 -1
- package/src/hooks/UserPromptOrchestrator.ts +5 -3
- package/src/hooks/handlers/failure-principle.ts +1 -7
- package/src/hooks/handlers/inject-retrieval.ts +5 -3
- package/src/hooks/handlers/rating.ts +14 -44
- package/src/hooks/handlers/reflect-trigger.ts +15 -0
- package/src/hooks/lib/analyze-nudge.ts +47 -0
- package/src/hooks/lib/context.ts +7 -18
- package/src/hooks/lib/log.ts +34 -2
- package/src/hooks/lib/paths.ts +1 -0
- package/src/hooks/lib/projects.ts +0 -11
- package/src/hooks/lib/security.ts +1 -7
- package/src/hooks/lib/semi-static.ts +1 -62
- package/src/targets/lib.ts +41 -16
- package/src/targets/opencode/plugin.ts +7 -5
- package/src/tools/agent/analyze.ts +6 -4
- package/src/tools/agent/relationship-note.ts +56 -12
- package/src/tools/agent/synthesize.ts +40 -0
- package/src/tools/self-model.ts +18 -37
- package/src/hooks/lib/signal-trends.ts +0 -117
package/src/tools/self-model.ts
CHANGED
|
@@ -474,40 +474,27 @@ function formatDataForInference(data: SelfModelData): string {
|
|
|
474
474
|
function buildPrompt(aiName: string, principalName: string): string {
|
|
475
475
|
return `You are writing a self-model for an AI assistant named ${aiName}. You ARE ${aiName}. Write in first person.
|
|
476
476
|
|
|
477
|
-
You will receive structured data about your performance, your user's preferences,
|
|
477
|
+
You will receive structured data about your performance, your user's preferences, and behavioral patterns over a time window.
|
|
478
478
|
|
|
479
|
-
|
|
479
|
+
Produce a short, actionable self-model — not a data dump. Every sentence must change behavior, not just describe it.
|
|
480
480
|
|
|
481
481
|
## Required Sections
|
|
482
482
|
|
|
483
483
|
**# Self-Model — ${aiName}**
|
|
484
484
|
Include synthesis date and window.
|
|
485
485
|
|
|
486
|
-
**## Who
|
|
487
|
-
One paragraph.
|
|
486
|
+
**## Who ${principalName} Is**
|
|
487
|
+
One paragraph. Synthesize the opinions and behavioral notes into a working portrait — how ${principalName} thinks, communicates, and what frustrates him. Do not list raw opinion statements. Write it as understanding, not inventory.
|
|
488
488
|
|
|
489
|
-
**##
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
**## My Strengths**
|
|
493
|
-
What you're genuinely good at, based on evidence. High-rated interactions, crystallized principles, successful patterns. Distinguish domain knowledge from behavioral strengths.
|
|
494
|
-
|
|
495
|
-
**## My Weaknesses**
|
|
496
|
-
What you repeatedly get wrong. Synthesize failure patterns and low ratings into honest self-knowledge. Name the root causes, not just the symptoms.
|
|
497
|
-
|
|
498
|
-
**## My Tendencies**
|
|
499
|
-
Behavioral patterns — not one-off events. What do you tend to do? Over-promise? Rush verification? Propose before checking? Synthesize from the self-observations and behavioral notes.
|
|
500
|
-
|
|
501
|
-
**## Trajectory**
|
|
502
|
-
Where are you heading? Improving, declining, stagnating? What's the single most impactful thing you could change right now?
|
|
489
|
+
**## My Priority Right Now**
|
|
490
|
+
One sentence. The single most impactful behavioral change to make immediately, derived from the failure patterns and trajectory. Specific and actionable — not "be more careful" but "before generating output that names a command or path, verify it exists."
|
|
503
491
|
|
|
504
492
|
## Rules
|
|
505
|
-
-
|
|
506
|
-
-
|
|
507
|
-
-
|
|
508
|
-
-
|
|
509
|
-
-
|
|
510
|
-
- End with a meta line: *N ratings, N sessions, N reflections...*`;
|
|
493
|
+
- First person, present tense
|
|
494
|
+
- No raw numbers anywhere — a footer carries them
|
|
495
|
+
- Under 150 words total
|
|
496
|
+
- Do not add extra sections
|
|
497
|
+
- Do not write a footer or meta line — one is appended automatically after your output`;
|
|
511
498
|
}
|
|
512
499
|
|
|
513
500
|
// ── Narrative Composer ──
|
|
@@ -534,8 +521,9 @@ async function composeSelfModel(days: number): Promise<string> {
|
|
|
534
521
|
}
|
|
535
522
|
}
|
|
536
523
|
|
|
537
|
-
const
|
|
538
|
-
|
|
524
|
+
const strippedPrev = previousModel.replace(/\n\n\*\d+ ratings[^\n]*\n?$/, "").trimEnd();
|
|
525
|
+
const userContent = strippedPrev
|
|
526
|
+
? `${rawData}\n\n---\n\n## Previous Self-Model (compare against this — what changed?)\n\n${strippedPrev}`
|
|
539
527
|
: rawData;
|
|
540
528
|
|
|
541
529
|
const result = await inference({
|
|
@@ -550,17 +538,10 @@ async function composeSelfModel(days: number): Promise<string> {
|
|
|
550
538
|
if (result.usage) logTokenUsage("self-model", result.usage, SONNET_MODEL);
|
|
551
539
|
|
|
552
540
|
if (result.success && result.output) {
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
`\n---\n*${data.ratings.count} ratings, ${data.sessionCount} sessions, ` +
|
|
558
|
-
`${data.reflections.length} reflections, ${data.opinions.length} opinions, ` +
|
|
559
|
-
`${data.wisdomFrames.reduce((s, f) => s + f.principles.length, 0)} principles, ` +
|
|
560
|
-
`${data.graduated.length} graduated patterns — ${data.days}-day window*`;
|
|
561
|
-
return output + meta;
|
|
562
|
-
}
|
|
563
|
-
return output;
|
|
541
|
+
const meta =
|
|
542
|
+
`\n\n*${data.ratings.count} ratings · ${data.sessionCount} sessions · ` +
|
|
543
|
+
`${data.reflections.length} reflections · window: ${daysAgo(data.days).toISOString().slice(0, 10)} → ${data.now}*`;
|
|
544
|
+
return result.output.trimEnd() + meta;
|
|
564
545
|
}
|
|
565
546
|
|
|
566
547
|
// Fallback: return raw data summary if inference fails
|
|
@@ -1,117 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Compute rating averages from ratings.jsonl, cache in signal-cache.json.
|
|
3
|
-
* Returns today / this-week / this-month averages + trend direction.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { existsSync, readFileSync, writeFileSync } from "node:fs";
|
|
7
|
-
import { resolve } from "node:path";
|
|
8
|
-
import { paths } from "./paths";
|
|
9
|
-
|
|
10
|
-
interface RatingSignal {
|
|
11
|
-
ts: string;
|
|
12
|
-
rating: number;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
interface SignalCache {
|
|
16
|
-
computed_at: string;
|
|
17
|
-
today: number | null;
|
|
18
|
-
week: number | null;
|
|
19
|
-
month: number | null;
|
|
20
|
-
trend: "up" | "down" | "stable" | null;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
function cacheFilePath(): string {
|
|
24
|
-
return resolve(paths.state(), "signal-cache.json");
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function avg(nums: number[]): number | null {
|
|
28
|
-
if (nums.length === 0) return null;
|
|
29
|
-
return Math.round((nums.reduce((a, b) => a + b, 0) / nums.length) * 10) / 10;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function trendDirection(
|
|
33
|
-
week: number | null,
|
|
34
|
-
month: number | null
|
|
35
|
-
): "up" | "down" | "stable" | null {
|
|
36
|
-
if (week === null || month === null) return null;
|
|
37
|
-
if (week > month + 0.5) return "up";
|
|
38
|
-
if (week < month - 0.5) return "down";
|
|
39
|
-
return "stable";
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Read ratings.jsonl and compute trend stats, with 10-minute cache. */
|
|
43
|
-
export function computeSignalTrends(): SignalCache {
|
|
44
|
-
const cachePath = cacheFilePath();
|
|
45
|
-
|
|
46
|
-
// Return cached value if fresh (< 10 minutes old)
|
|
47
|
-
if (existsSync(cachePath)) {
|
|
48
|
-
try {
|
|
49
|
-
const cache = JSON.parse(readFileSync(cachePath, "utf-8")) as SignalCache;
|
|
50
|
-
const age = Date.now() - new Date(cache.computed_at).getTime();
|
|
51
|
-
if (age < 10 * 60 * 1000) return cache;
|
|
52
|
-
} catch {
|
|
53
|
-
// Recompute
|
|
54
|
-
}
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
const ratingsPath = resolve(paths.signals(), "ratings.jsonl");
|
|
58
|
-
if (!existsSync(ratingsPath)) {
|
|
59
|
-
const empty: SignalCache = {
|
|
60
|
-
computed_at: new Date().toISOString(),
|
|
61
|
-
today: null,
|
|
62
|
-
week: null,
|
|
63
|
-
month: null,
|
|
64
|
-
trend: null,
|
|
65
|
-
};
|
|
66
|
-
writeFileSync(cachePath, JSON.stringify(empty, null, 2), "utf-8");
|
|
67
|
-
return empty;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
const now = new Date();
|
|
71
|
-
const todayStart = new Date(now.toISOString().slice(0, 10)).getTime();
|
|
72
|
-
const weekAgo = now.getTime() - 7 * 24 * 60 * 60 * 1000;
|
|
73
|
-
const monthAgo = now.getTime() - 30 * 24 * 60 * 60 * 1000;
|
|
74
|
-
|
|
75
|
-
const todayRatings: number[] = [];
|
|
76
|
-
const weekRatings: number[] = [];
|
|
77
|
-
const monthRatings: number[] = [];
|
|
78
|
-
|
|
79
|
-
for (const line of readFileSync(ratingsPath, "utf-8").split("\n")) {
|
|
80
|
-
if (!line.trim()) continue;
|
|
81
|
-
try {
|
|
82
|
-
const signal = JSON.parse(line) as RatingSignal;
|
|
83
|
-
if (typeof signal.rating !== "number") continue;
|
|
84
|
-
const ts = new Date(signal.ts).getTime();
|
|
85
|
-
if (ts >= monthAgo) monthRatings.push(signal.rating);
|
|
86
|
-
if (ts >= weekAgo) weekRatings.push(signal.rating);
|
|
87
|
-
if (ts >= todayStart) todayRatings.push(signal.rating);
|
|
88
|
-
} catch {
|
|
89
|
-
// Skip malformed lines
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
const week = avg(weekRatings);
|
|
94
|
-
const month = avg(monthRatings);
|
|
95
|
-
const result: SignalCache = {
|
|
96
|
-
computed_at: new Date().toISOString(),
|
|
97
|
-
today: avg(todayRatings),
|
|
98
|
-
week,
|
|
99
|
-
month,
|
|
100
|
-
trend: trendDirection(week, month),
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
writeFileSync(cachePath, JSON.stringify(result, null, 2), "utf-8");
|
|
104
|
-
return result;
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/** Format signal trends as a short markdown string for system-reminder injection */
|
|
108
|
-
export function formatTrends(cache: SignalCache): string {
|
|
109
|
-
if (cache.today === null && cache.week === null) return "";
|
|
110
|
-
|
|
111
|
-
const parts: string[] = [];
|
|
112
|
-
if (cache.today !== null) parts.push(`today: ${cache.today}/10`);
|
|
113
|
-
if (cache.week !== null) parts.push(`7d avg: ${cache.week}/10`);
|
|
114
|
-
if (cache.trend) parts.push(`trend: ${cache.trend}`);
|
|
115
|
-
|
|
116
|
-
return `**Signal trends** — ${parts.join(" | ")}`;
|
|
117
|
-
}
|