portable-agent-layer 0.35.0 → 0.37.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 +2 -1
- package/assets/skills/analyze-pdf/tools/pdf-download.ts +1 -1
- package/assets/skills/analyze-youtube/tools/youtube-analyze.ts +1 -1
- package/assets/skills/consulting-report/tools/dev.ts +2 -2
- package/assets/skills/consulting-report/tools/generate-pdf.ts +9 -9
- package/assets/skills/consulting-report/tools/scaffold.ts +2 -2
- package/assets/skills/create-pdf/tools/md-to-html-pdf.ts +2 -2
- package/assets/skills/opinion/tools/opinion.ts +3 -2
- package/assets/skills/presentation/SKILL.md +1 -1
- package/assets/skills/presentation/tools/doctor.ts +2 -5
- package/assets/skills/presentation/tools/lib/inline.ts +6 -11
- package/assets/skills/presentation/tools/lib/lint-helpers.ts +2 -2
- package/assets/skills/presentation/tools/lib/lint-rules.ts +5 -2
- package/assets/skills/presentation/tools/setup-template.ts +10 -7
- package/assets/skills/projects/SKILL.md +44 -21
- package/assets/skills/research/tools/gemini-search.ts +2 -2
- package/assets/skills/research/tools/grok-search.ts +2 -2
- package/assets/skills/research/tools/perplexity-search.ts +2 -2
- package/assets/skills/telos/SKILL.md +7 -52
- package/assets/skills/telos/tools/update-telos.ts +0 -1
- package/assets/templates/PAL/ALGORITHM.md +54 -5
- package/assets/templates/PAL/PROJECT_LIFECYCLE.md +48 -0
- package/assets/templates/PAL/README.md +1 -1
- package/assets/templates/PAL/STEERING_RULES.md +4 -0
- package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +32 -17
- package/assets/templates/PAL/WORK_TRACKING.md +1 -1
- package/assets/templates/hooks.codex.json +44 -0
- package/assets/templates/hooks.cursor.json +11 -5
- package/assets/templates/pal-settings.json +1 -3
- package/assets/templates/settings.claude.json +2 -1
- package/package.json +2 -1
- package/src/cli/index.ts +112 -14
- package/src/cli/migrate.ts +299 -0
- package/src/cli/setup-identity.ts +3 -3
- package/src/cli/setup-telos.ts +12 -80
- package/src/hooks/CompactRecover.ts +11 -5
- package/src/hooks/LoadContext.ts +35 -11
- package/src/hooks/PreCompactPersist.ts +26 -34
- package/src/hooks/SecurityValidator.ts +43 -21
- package/src/hooks/StopOrchestrator.ts +4 -1
- package/src/hooks/UserPromptOrchestrator.ts +4 -2
- package/src/hooks/handlers/auto-graduate.ts +2 -2
- package/src/hooks/handlers/backup.ts +3 -3
- package/src/hooks/handlers/context-digests.ts +74 -0
- package/src/hooks/handlers/failure.ts +5 -3
- package/src/hooks/handlers/inject-retrieval.ts +29 -6
- package/src/hooks/handlers/persist-last-exchange.ts +76 -0
- package/src/hooks/handlers/rating.ts +2 -1
- package/src/hooks/handlers/readme-sync.ts +3 -2
- package/src/hooks/handlers/session-intelligence.ts +17 -93
- package/src/hooks/handlers/session-name.ts +2 -2
- package/src/hooks/handlers/synthesis.ts +5 -2
- package/src/hooks/handlers/update-counts.ts +3 -2
- package/src/hooks/lib/agent.ts +20 -18
- package/src/hooks/lib/claude-md.ts +69 -14
- package/src/hooks/lib/context.ts +92 -246
- package/src/hooks/lib/entities.ts +7 -7
- package/src/hooks/lib/frontmatter.ts +4 -4
- package/src/hooks/lib/graduation.ts +7 -6
- package/src/hooks/lib/inference.ts +6 -2
- package/src/hooks/lib/learning-category.ts +1 -1
- package/src/hooks/lib/learning-store.ts +6 -1
- package/src/hooks/lib/notify.ts +2 -2
- package/src/hooks/lib/opinions.ts +3 -3
- package/src/hooks/lib/paths.ts +2 -0
- package/src/hooks/lib/projects.ts +142 -74
- package/src/hooks/lib/readme-sync.ts +1 -1
- package/src/hooks/lib/relationship.ts +4 -16
- package/src/hooks/lib/retrieval-index.ts +5 -3
- package/src/hooks/lib/retrieval.ts +11 -12
- package/src/hooks/lib/security.ts +24 -18
- package/src/hooks/lib/semi-static.ts +188 -0
- package/src/hooks/lib/session-names.ts +1 -1
- package/src/hooks/lib/settings.ts +1 -1
- package/src/hooks/lib/setup.ts +2 -65
- package/src/hooks/lib/signals.ts +2 -2
- package/src/hooks/lib/stdin.ts +1 -1
- package/src/hooks/lib/stop.ts +16 -6
- package/src/hooks/lib/token-usage.ts +1 -2
- package/src/hooks/lib/transcript.ts +1 -1
- package/src/hooks/lib/wisdom.ts +5 -5
- package/src/hooks/lib/work-tracking.ts +8 -14
- package/src/targets/claude/uninstall.ts +1 -1
- package/src/targets/codex/install.ts +95 -0
- package/src/targets/codex/uninstall.ts +70 -0
- package/src/targets/copilot/install.ts +39 -8
- package/src/targets/copilot/uninstall.ts +58 -17
- package/src/targets/cursor/install.ts +8 -0
- package/src/targets/cursor/uninstall.ts +18 -1
- package/src/targets/lib.ts +166 -14
- package/src/targets/opencode/install.ts +29 -1
- package/src/targets/opencode/plugin.ts +23 -12
- package/src/targets/opencode/uninstall.ts +30 -3
- package/src/tools/agent/algorithm-reflect.ts +1 -1
- package/src/tools/agent/analyze.ts +18 -18
- package/src/tools/agent/handoff-note.ts +116 -0
- package/src/tools/agent/project.ts +375 -75
- package/src/tools/agent/relationship-note.ts +51 -0
- package/src/tools/agent/synthesize.ts +6 -42
- package/src/tools/agent/thread.ts +15 -14
- package/src/tools/agent/wisdom-frame.ts +9 -3
- package/src/tools/import.ts +1 -1
- package/src/tools/relationship-reflect.ts +15 -13
- package/src/tools/self-model.ts +23 -19
- package/src/tools/session-summary.ts +3 -3
- package/src/tools/token-cost.ts +15 -16
- package/assets/skills/telos/tools/update-projects.ts +0 -106
- package/assets/templates/telos/PROJECTS.md +0 -7
|
@@ -17,6 +17,7 @@ import { existsSync, readdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
|
17
17
|
import { resolve } from "node:path";
|
|
18
18
|
import { parseArgs } from "node:util";
|
|
19
19
|
import { ensureDir, paths } from "../../hooks/lib/paths";
|
|
20
|
+
import { readJsonl } from "../self-model";
|
|
20
21
|
|
|
21
22
|
// ── Config ──
|
|
22
23
|
|
|
@@ -27,7 +28,6 @@ const SYNTHESIS_TTL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
|
27
28
|
interface SynthesisState {
|
|
28
29
|
timestamp: string;
|
|
29
30
|
days: number;
|
|
30
|
-
threads: { id: string; cwd?: string; title: string; context: string; opened: string }[];
|
|
31
31
|
sessions: { date: string; titles: string[] }[];
|
|
32
32
|
sessionCount: number;
|
|
33
33
|
ratings: {
|
|
@@ -74,14 +74,6 @@ function shouldRun(force: boolean): boolean {
|
|
|
74
74
|
}
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
function readJsonl<T>(path: string): T[] {
|
|
78
|
-
if (!existsSync(path)) return [];
|
|
79
|
-
return readFileSync(path, "utf-8")
|
|
80
|
-
.split("\n")
|
|
81
|
-
.filter((l) => l.trim())
|
|
82
|
-
.map((l) => JSON.parse(l) as T);
|
|
83
|
-
}
|
|
84
|
-
|
|
85
77
|
function daysAgo(days: number): Date {
|
|
86
78
|
return new Date(Date.now() - days * 24 * 60 * 60 * 1000);
|
|
87
79
|
}
|
|
@@ -100,28 +92,6 @@ function safeReaddir(dir: string): string[] {
|
|
|
100
92
|
|
|
101
93
|
// ── Data readers ──
|
|
102
94
|
|
|
103
|
-
interface Thread {
|
|
104
|
-
id: string;
|
|
105
|
-
cwd?: string;
|
|
106
|
-
title: string;
|
|
107
|
-
context: string;
|
|
108
|
-
status: string;
|
|
109
|
-
created: string;
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
function getOpenThreads(): SynthesisState["threads"] {
|
|
113
|
-
const threads = readJsonl<Thread>(resolve(stateDir(), "threads.jsonl"));
|
|
114
|
-
return threads
|
|
115
|
-
.filter((t) => t.status === "open")
|
|
116
|
-
.map((t) => ({
|
|
117
|
-
id: t.id,
|
|
118
|
-
cwd: t.cwd,
|
|
119
|
-
title: t.title,
|
|
120
|
-
context: t.context,
|
|
121
|
-
opened: formatDate(t.created),
|
|
122
|
-
}));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
95
|
interface Reflection {
|
|
126
96
|
timestamp: string;
|
|
127
97
|
cwd?: string;
|
|
@@ -210,12 +180,9 @@ function getRatingStats(since: Date): SynthesisState["ratings"] {
|
|
|
210
180
|
const secondHalf = ratings.slice(mid);
|
|
211
181
|
const firstAvg = firstHalf.reduce((s, r) => s + r.rating, 0) / firstHalf.length;
|
|
212
182
|
const secondAvg = secondHalf.reduce((s, r) => s + r.rating, 0) / secondHalf.length;
|
|
213
|
-
const
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
: secondAvg - firstAvg < -0.5
|
|
217
|
-
? "declining"
|
|
218
|
-
: "stable";
|
|
183
|
+
const diff = secondAvg - firstAvg;
|
|
184
|
+
const decliningOrStable = diff < -0.5 ? "declining" : "stable";
|
|
185
|
+
const trend = diff > 0.5 ? "improving" : decliningOrStable;
|
|
219
186
|
|
|
220
187
|
return {
|
|
221
188
|
count: ratings.length,
|
|
@@ -251,8 +218,8 @@ function getRecentSessions(since: Date): {
|
|
|
251
218
|
|
|
252
219
|
const content = readFileSync(resolve(monthDir, file), "utf-8");
|
|
253
220
|
const titleMatch =
|
|
254
|
-
|
|
255
|
-
|
|
221
|
+
new RegExp(/^title:\s*"?(.+?)"?\s*$/m).exec(content) ??
|
|
222
|
+
new RegExp(/^\*\*Title:\*\*\s*(.+?)\s*$/m).exec(content);
|
|
256
223
|
const title = titleMatch?.[1] ?? file.replace(/\.md$/, "");
|
|
257
224
|
|
|
258
225
|
const existing = byDate.get(isoDate) ?? [];
|
|
@@ -280,7 +247,6 @@ export function writeSynthesis(state: SynthesisState): string {
|
|
|
280
247
|
|
|
281
248
|
export function synthesize(days: number): SynthesisState {
|
|
282
249
|
const since = daysAgo(days);
|
|
283
|
-
const threads = getOpenThreads();
|
|
284
250
|
const { sessions, count: sessionCount } = getRecentSessions(since);
|
|
285
251
|
const ratings = getRatingStats(since);
|
|
286
252
|
const algorithm = getAlgorithmStats(since);
|
|
@@ -288,7 +254,6 @@ export function synthesize(days: number): SynthesisState {
|
|
|
288
254
|
return {
|
|
289
255
|
timestamp: new Date().toISOString(),
|
|
290
256
|
days,
|
|
291
|
-
threads,
|
|
292
257
|
sessions,
|
|
293
258
|
sessionCount,
|
|
294
259
|
ratings,
|
|
@@ -346,7 +311,6 @@ Output: ~/.pal/memory/state/synthesis.json
|
|
|
346
311
|
{
|
|
347
312
|
success: true,
|
|
348
313
|
path: sp,
|
|
349
|
-
threads: state.threads.length,
|
|
350
314
|
sessions: state.sessionCount,
|
|
351
315
|
ratings: state.ratings.count,
|
|
352
316
|
reflections: state.algorithm.reflectionCount,
|
|
@@ -18,7 +18,7 @@ import { ensureDir, paths } from "../../hooks/lib/paths";
|
|
|
18
18
|
|
|
19
19
|
// ── Types ──
|
|
20
20
|
|
|
21
|
-
interface Thread {
|
|
21
|
+
export interface Thread {
|
|
22
22
|
id: string;
|
|
23
23
|
cwd: string;
|
|
24
24
|
title: string;
|
|
@@ -38,16 +38,20 @@ function generateId(): string {
|
|
|
38
38
|
return Date.now().toString(36) + Math.random().toString(36).slice(2, 5);
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
-
function readThreads(): Thread[] {
|
|
41
|
+
export function readThreads(): Thread[] {
|
|
42
42
|
const p = threadsPath();
|
|
43
43
|
if (!existsSync(p)) return [];
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
44
|
+
try {
|
|
45
|
+
return readFileSync(p, "utf-8")
|
|
46
|
+
.split("\n")
|
|
47
|
+
.filter((l) => l.trim())
|
|
48
|
+
.map((l) => JSON.parse(l) as Thread);
|
|
49
|
+
} catch {
|
|
50
|
+
return [];
|
|
51
|
+
}
|
|
48
52
|
}
|
|
49
53
|
|
|
50
|
-
function writeThreads(threads: Thread[]): void {
|
|
54
|
+
export function writeThreads(threads: Thread[]): void {
|
|
51
55
|
writeFileSync(
|
|
52
56
|
threadsPath(),
|
|
53
57
|
`${threads.map((t) => JSON.stringify(t)).join("\n")}\n`,
|
|
@@ -110,13 +114,10 @@ function run() {
|
|
|
110
114
|
},
|
|
111
115
|
});
|
|
112
116
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
: values.list
|
|
118
|
-
? "list"
|
|
119
|
-
: null;
|
|
117
|
+
let cmd: string | null = null;
|
|
118
|
+
if (values.add) cmd = "add";
|
|
119
|
+
else if (values.resolve) cmd = "resolve";
|
|
120
|
+
else if (values.list) cmd = "list";
|
|
120
121
|
|
|
121
122
|
if (values.help || !cmd) {
|
|
122
123
|
console.log(`
|
|
@@ -39,7 +39,7 @@ function date(): string {
|
|
|
39
39
|
}
|
|
40
40
|
|
|
41
41
|
function parseObservationCount(content: string): number {
|
|
42
|
-
const match =
|
|
42
|
+
const match = new RegExp(/\*\*Observation Count:\*\*\s*(\d+)/).exec(content);
|
|
43
43
|
return match ? parseInt(match[1], 10) : 0;
|
|
44
44
|
}
|
|
45
45
|
|
|
@@ -90,6 +90,12 @@ export function updateFrame(
|
|
|
90
90
|
if (!existsSync(framePath)) {
|
|
91
91
|
mkdirSync(framesDir, { recursive: true });
|
|
92
92
|
|
|
93
|
+
const contextualRuleEntry =
|
|
94
|
+
type === "contextual-rule" ? `- ${observation} (${date()})` : "*None yet.*";
|
|
95
|
+
const antiPatternEntry =
|
|
96
|
+
type === "anti-pattern"
|
|
97
|
+
? `### ${observation}\n- **Severity:** Medium\n- **Frequency:** Observed`
|
|
98
|
+
: "*None yet.*";
|
|
93
99
|
const content = `# Frame: ${domain.charAt(0).toUpperCase() + domain.slice(1)}
|
|
94
100
|
|
|
95
101
|
## Meta
|
|
@@ -107,13 +113,13 @@ export function updateFrame(
|
|
|
107
113
|
|
|
108
114
|
## Contextual Rules
|
|
109
115
|
|
|
110
|
-
${
|
|
116
|
+
${contextualRuleEntry}
|
|
111
117
|
|
|
112
118
|
---
|
|
113
119
|
|
|
114
120
|
## Anti-Patterns
|
|
115
121
|
|
|
116
|
-
${
|
|
122
|
+
${antiPatternEntry}
|
|
117
123
|
|
|
118
124
|
---
|
|
119
125
|
|
package/src/tools/import.ts
CHANGED
|
@@ -37,7 +37,7 @@ interface Rating {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
interface ParsedNote {
|
|
40
|
-
type: "W" | "O" | "
|
|
40
|
+
type: "W" | "O" | "Session";
|
|
41
41
|
text: string;
|
|
42
42
|
confidence?: number;
|
|
43
43
|
date: string;
|
|
@@ -84,17 +84,17 @@ export function loadNotes(daysBack: number): ParsedNote[] {
|
|
|
84
84
|
let currentTime = "";
|
|
85
85
|
|
|
86
86
|
for (const line of content.split("\n")) {
|
|
87
|
-
const timeMatch =
|
|
87
|
+
const timeMatch = new RegExp(/^## (\d{2}:\d{2})/).exec(line);
|
|
88
88
|
if (timeMatch) {
|
|
89
89
|
currentTime = timeMatch[1];
|
|
90
90
|
continue;
|
|
91
91
|
}
|
|
92
92
|
|
|
93
93
|
// O(c=0.85): text or B(c=0.85): text
|
|
94
|
-
const obMatch =
|
|
94
|
+
const obMatch = new RegExp(/^- ([OB])\(c=([\d.]+)\):\s*(.+)$/).exec(line);
|
|
95
95
|
if (obMatch) {
|
|
96
96
|
notes.push({
|
|
97
|
-
type: obMatch[1] as "O" | "
|
|
97
|
+
type: obMatch[1] as "O" | "Session",
|
|
98
98
|
confidence: Number.parseFloat(obMatch[2]),
|
|
99
99
|
text: obMatch[3],
|
|
100
100
|
date: dateStr,
|
|
@@ -104,7 +104,7 @@ export function loadNotes(daysBack: number): ParsedNote[] {
|
|
|
104
104
|
}
|
|
105
105
|
|
|
106
106
|
// W: text
|
|
107
|
-
const worldMatch =
|
|
107
|
+
const worldMatch = new RegExp(/^- W:\s*(.+)$/).exec(line);
|
|
108
108
|
if (worldMatch) {
|
|
109
109
|
notes.push({
|
|
110
110
|
type: "W",
|
|
@@ -261,19 +261,21 @@ function correlateRatings(ratings: Rating[]): string[] {
|
|
|
261
261
|
const highRatings = ratings.filter((r) => r.rating >= 7);
|
|
262
262
|
|
|
263
263
|
if (lowRatings.length > 0) {
|
|
264
|
+
const lowContexts = lowRatings
|
|
265
|
+
.slice(0, 3)
|
|
266
|
+
.map((r) => `"${r.context.slice(0, 60)}"`)
|
|
267
|
+
.join(", ");
|
|
264
268
|
correlations.push(
|
|
265
|
-
`${lowRatings.length} low ratings (<=4) — common contexts: ${
|
|
266
|
-
.slice(0, 3)
|
|
267
|
-
.map((r) => `"${r.context.slice(0, 60)}"`)
|
|
268
|
-
.join(", ")}`
|
|
269
|
+
`${lowRatings.length} low ratings (<=4) — common contexts: ${lowContexts}`
|
|
269
270
|
);
|
|
270
271
|
}
|
|
271
272
|
if (highRatings.length > 0) {
|
|
273
|
+
const highContexts = highRatings
|
|
274
|
+
.slice(0, 3)
|
|
275
|
+
.map((r) => `"${r.context.slice(0, 60)}"`)
|
|
276
|
+
.join(", ");
|
|
272
277
|
correlations.push(
|
|
273
|
-
`${highRatings.length} high ratings (>=7) — common contexts: ${
|
|
274
|
-
.slice(0, 3)
|
|
275
|
-
.map((r) => `"${r.context.slice(0, 60)}"`)
|
|
276
|
-
.join(", ")}`
|
|
278
|
+
`${highRatings.length} high ratings (>=7) — common contexts: ${highContexts}`
|
|
277
279
|
);
|
|
278
280
|
}
|
|
279
281
|
|
package/src/tools/self-model.ts
CHANGED
|
@@ -71,7 +71,7 @@ interface AlgorithmReflection {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
interface RelationshipNote {
|
|
74
|
-
type: "O" | "W" | "
|
|
74
|
+
type: "O" | "W" | "Session";
|
|
75
75
|
content: string;
|
|
76
76
|
confidence?: number;
|
|
77
77
|
date: string;
|
|
@@ -107,12 +107,16 @@ function shouldRun(force: boolean): boolean {
|
|
|
107
107
|
}
|
|
108
108
|
}
|
|
109
109
|
|
|
110
|
-
function readJsonl<T>(path: string): T[] {
|
|
110
|
+
export function readJsonl<T>(path: string): T[] {
|
|
111
111
|
if (!existsSync(path)) return [];
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
112
|
+
try {
|
|
113
|
+
return readFileSync(path, "utf-8")
|
|
114
|
+
.split("\n")
|
|
115
|
+
.filter((l) => l.trim())
|
|
116
|
+
.map((l) => JSON.parse(l) as T);
|
|
117
|
+
} catch {
|
|
118
|
+
return [];
|
|
119
|
+
}
|
|
116
120
|
}
|
|
117
121
|
|
|
118
122
|
function safeReadJson<T>(path: string, fallback: T): T {
|
|
@@ -261,7 +265,9 @@ function readRelationshipNotes(since: Date): RelationshipNote[] {
|
|
|
261
265
|
const notes: RelationshipNote[] = [];
|
|
262
266
|
const sinceStr = formatDate(since.toISOString());
|
|
263
267
|
|
|
264
|
-
for (const monthDir of safeReaddir(baseDir).filter((d) =>
|
|
268
|
+
for (const monthDir of safeReaddir(baseDir).filter((d) =>
|
|
269
|
+
new RegExp(/^\d{4}-\d{2}$/).exec(d)
|
|
270
|
+
)) {
|
|
265
271
|
const fullMonthDir = resolve(baseDir, monthDir);
|
|
266
272
|
for (const file of safeReaddir(fullMonthDir).filter((f) => f.endsWith(".md"))) {
|
|
267
273
|
const dateStr = file.replace(/\.md$/, "");
|
|
@@ -275,7 +281,7 @@ function readRelationshipNotes(since: Date): RelationshipNote[] {
|
|
|
275
281
|
const noteContent = trimmed.substring(2);
|
|
276
282
|
|
|
277
283
|
// Parse O(c=X.XX): ..., W: ..., B: ...
|
|
278
|
-
const opinionMatch =
|
|
284
|
+
const opinionMatch = new RegExp(/^O\(c=([\d.]+)\):\s*(.+)$/).exec(noteContent);
|
|
279
285
|
if (opinionMatch) {
|
|
280
286
|
notes.push({
|
|
281
287
|
type: "O",
|
|
@@ -286,15 +292,15 @@ function readRelationshipNotes(since: Date): RelationshipNote[] {
|
|
|
286
292
|
continue;
|
|
287
293
|
}
|
|
288
294
|
|
|
289
|
-
const wisdomMatch =
|
|
295
|
+
const wisdomMatch = new RegExp(/^W:\s*(.+)$/).exec(noteContent);
|
|
290
296
|
if (wisdomMatch) {
|
|
291
297
|
notes.push({ type: "W", content: wisdomMatch[1], date: dateStr });
|
|
292
298
|
continue;
|
|
293
299
|
}
|
|
294
300
|
|
|
295
|
-
const behaviorMatch =
|
|
301
|
+
const behaviorMatch = new RegExp(/^Session:\s*(.+)$/).exec(noteContent);
|
|
296
302
|
if (behaviorMatch) {
|
|
297
|
-
notes.push({ type: "
|
|
303
|
+
notes.push({ type: "Session", content: behaviorMatch[1], date: dateStr });
|
|
298
304
|
}
|
|
299
305
|
}
|
|
300
306
|
}
|
|
@@ -376,7 +382,7 @@ function gatherData(days: number): SelfModelData {
|
|
|
376
382
|
wisdomFrames,
|
|
377
383
|
graduated,
|
|
378
384
|
reflections,
|
|
379
|
-
behaviorNotes: relNotes.filter((n) => n.type === "
|
|
385
|
+
behaviorNotes: relNotes.filter((n) => n.type === "Session").map((n) => n.content),
|
|
380
386
|
wisdomNotes: relNotes.filter((n) => n.type === "W").map((n) => n.content),
|
|
381
387
|
selfObservations: reflections.map((r) => r.q1).filter(Boolean),
|
|
382
388
|
algorithmObservations: reflections.map((r) => r.q2).filter(Boolean),
|
|
@@ -388,12 +394,10 @@ function gatherData(days: number): SelfModelData {
|
|
|
388
394
|
function formatDataForInference(data: SelfModelData): string {
|
|
389
395
|
const sections: string[] = [];
|
|
390
396
|
|
|
391
|
-
sections.push(`## Raw Data — ${data.days}-day window, ${data.now}`);
|
|
392
|
-
sections.push(`Sessions: ${data.sessionCount}`);
|
|
393
|
-
sections.push(
|
|
394
|
-
`Ratings: ${data.ratings.count} total, ${data.ratings.avg}/10 avg, recent ${data.ratings.recentAvg}/10, trend ${data.ratings.trend}`
|
|
395
|
-
);
|
|
396
397
|
sections.push(
|
|
398
|
+
`## Raw Data — ${data.days}-day window, ${data.now}`,
|
|
399
|
+
`Sessions: ${data.sessionCount}`,
|
|
400
|
+
`Ratings: ${data.ratings.count} total, ${data.ratings.avg}/10 avg, recent ${data.ratings.recentAvg}/10, trend ${data.ratings.trend}`,
|
|
397
401
|
`${data.ratings.highCount} high (8+), ${data.ratings.lowCount} low (<=3)`
|
|
398
402
|
);
|
|
399
403
|
|
|
@@ -508,7 +512,7 @@ Where are you heading? Improving, declining, stagnating? What's the single most
|
|
|
508
512
|
|
|
509
513
|
// ── Narrative Composer ──
|
|
510
514
|
|
|
511
|
-
|
|
515
|
+
async function composeSelfModel(days: number): Promise<string> {
|
|
512
516
|
const data = gatherData(days);
|
|
513
517
|
const rawData = formatDataForInference(data);
|
|
514
518
|
const id = loadSettingsIdentity();
|
|
@@ -668,4 +672,4 @@ Output: ~/.pal/memory/self-model.md (synthesized by Sonnet)
|
|
|
668
672
|
);
|
|
669
673
|
}
|
|
670
674
|
|
|
671
|
-
if (import.meta.main) run();
|
|
675
|
+
if (import.meta.main) await run();
|
|
@@ -27,7 +27,7 @@ interface Usage {
|
|
|
27
27
|
|
|
28
28
|
// ── Core Functions ──
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
function findSessionFile(
|
|
31
31
|
sessionId: string,
|
|
32
32
|
claudeDir: string
|
|
33
33
|
): { filepath: string; project: string } | null {
|
|
@@ -79,7 +79,7 @@ export function findSessionFile(
|
|
|
79
79
|
return latest;
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
|
|
82
|
+
function parseSession(filepath: string, sessionId: string): Usage {
|
|
83
83
|
const usage: Usage = {
|
|
84
84
|
input: 0,
|
|
85
85
|
output: 0,
|
|
@@ -122,7 +122,7 @@ export function parseSession(filepath: string, sessionId: string): Usage {
|
|
|
122
122
|
if (d.sessionId !== sessionId) continue;
|
|
123
123
|
|
|
124
124
|
if (d.timestamp) {
|
|
125
|
-
|
|
125
|
+
firstTs ??= d.timestamp;
|
|
126
126
|
lastTs = d.timestamp;
|
|
127
127
|
}
|
|
128
128
|
|
package/src/tools/token-cost.ts
CHANGED
|
@@ -17,7 +17,7 @@ import { palHome } from "../hooks/lib/paths";
|
|
|
17
17
|
|
|
18
18
|
// ── Types ──
|
|
19
19
|
|
|
20
|
-
|
|
20
|
+
interface Bucket {
|
|
21
21
|
input: number;
|
|
22
22
|
output: number;
|
|
23
23
|
cacheWrite5m: number;
|
|
@@ -27,7 +27,7 @@ export interface Bucket {
|
|
|
27
27
|
calls: number;
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
30
|
+
function emptyBucket(): Bucket {
|
|
31
31
|
return {
|
|
32
32
|
input: 0,
|
|
33
33
|
output: 0,
|
|
@@ -39,14 +39,14 @@ export function emptyBucket(): Bucket {
|
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
-
|
|
42
|
+
interface TimeBuckets {
|
|
43
43
|
today: Bucket;
|
|
44
44
|
week: Bucket;
|
|
45
45
|
month: Bucket;
|
|
46
46
|
total: Bucket;
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
|
|
49
|
+
function emptyTimeBuckets(): TimeBuckets {
|
|
50
50
|
return {
|
|
51
51
|
today: emptyBucket(),
|
|
52
52
|
week: emptyBucket(),
|
|
@@ -85,7 +85,7 @@ function costForUsage(
|
|
|
85
85
|
);
|
|
86
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
88
|
+
function addToBucket(
|
|
89
89
|
bucket: Bucket,
|
|
90
90
|
model: string,
|
|
91
91
|
input: number,
|
|
@@ -160,7 +160,7 @@ function addToTimeBuckets(
|
|
|
160
160
|
addToBucket(tb.today, model, input, output, cacheWrite5m, cacheWrite1h, cacheRead);
|
|
161
161
|
}
|
|
162
162
|
|
|
163
|
-
|
|
163
|
+
function readClaudeCode(projectFilter?: string): {
|
|
164
164
|
buckets: TimeBuckets;
|
|
165
165
|
byModel: Record<string, Bucket>;
|
|
166
166
|
byProject: Record<string, TimeBuckets>;
|
|
@@ -267,7 +267,7 @@ export function readClaudeCode(projectFilter?: string): {
|
|
|
267
267
|
monthAgo
|
|
268
268
|
);
|
|
269
269
|
|
|
270
|
-
|
|
270
|
+
byModel[model] ??= emptyBucket();
|
|
271
271
|
addToBucket(
|
|
272
272
|
byModel[model],
|
|
273
273
|
model,
|
|
@@ -278,7 +278,7 @@ export function readClaudeCode(projectFilter?: string): {
|
|
|
278
278
|
cr
|
|
279
279
|
);
|
|
280
280
|
|
|
281
|
-
|
|
281
|
+
byProject[projName] ??= emptyTimeBuckets();
|
|
282
282
|
addToTimeBuckets(
|
|
283
283
|
byProject[projName],
|
|
284
284
|
ts,
|
|
@@ -304,7 +304,7 @@ export function readClaudeCode(projectFilter?: string): {
|
|
|
304
304
|
|
|
305
305
|
// ── PAL inference ──
|
|
306
306
|
|
|
307
|
-
|
|
307
|
+
function readPalInference(): {
|
|
308
308
|
buckets: TimeBuckets;
|
|
309
309
|
byModel: Record<string, TimeBuckets>;
|
|
310
310
|
byCaller: Record<string, Bucket>;
|
|
@@ -346,7 +346,7 @@ export function readPalInference(): {
|
|
|
346
346
|
weekAgo,
|
|
347
347
|
monthAgo
|
|
348
348
|
);
|
|
349
|
-
|
|
349
|
+
byModel[e.model] ??= emptyTimeBuckets();
|
|
350
350
|
addToTimeBuckets(
|
|
351
351
|
byModel[e.model],
|
|
352
352
|
e.ts,
|
|
@@ -360,7 +360,7 @@ export function readPalInference(): {
|
|
|
360
360
|
weekAgo,
|
|
361
361
|
monthAgo
|
|
362
362
|
);
|
|
363
|
-
|
|
363
|
+
byCaller[e.caller] ??= emptyBucket();
|
|
364
364
|
addToBucket(byCaller[e.caller], e.model, e.inputTokens, e.outputTokens, 0, 0, 0);
|
|
365
365
|
} catch {
|
|
366
366
|
/* skip */
|
|
@@ -415,11 +415,10 @@ export function usage() {
|
|
|
415
415
|
|
|
416
416
|
for (const [model, tb] of Object.entries(pal.byModel)) {
|
|
417
417
|
if (tb.total.calls === 0) continue;
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
: model.replace("claude-", "");
|
|
418
|
+
let label: string;
|
|
419
|
+
if (model.includes("haiku")) label = "Haiku";
|
|
420
|
+
else if (model.includes("sonnet")) label = "Sonnet";
|
|
421
|
+
else label = model.replace("claude-", "");
|
|
423
422
|
console.log(`\n PAL Inference (${label})\n`);
|
|
424
423
|
printRow("Today", tb.today);
|
|
425
424
|
printRow("7d", tb.week);
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bun
|
|
2
|
-
/**
|
|
3
|
-
* UpdateProjects — Upsert a project row in PROJECTS.md by ID.
|
|
4
|
-
*
|
|
5
|
-
* Usage:
|
|
6
|
-
* bun update-projects.ts <id> "<row>" "<description>"
|
|
7
|
-
*
|
|
8
|
-
* - <id> is the value in the first column (e.g., "my-proj", "side-gig")
|
|
9
|
-
* - <row> is the full table row including the ID column (e.g., "| my-proj | My Project | Done | High | ... |")
|
|
10
|
-
* - If a row with that ID exists, it is replaced
|
|
11
|
-
* - If no row with that ID exists, it is appended
|
|
12
|
-
* - Creates a timestamped backup before modifying
|
|
13
|
-
* - Logs the change to updates.md
|
|
14
|
-
*/
|
|
15
|
-
|
|
16
|
-
import {
|
|
17
|
-
copyFileSync,
|
|
18
|
-
existsSync,
|
|
19
|
-
mkdirSync,
|
|
20
|
-
readFileSync,
|
|
21
|
-
writeFileSync,
|
|
22
|
-
} from "node:fs";
|
|
23
|
-
import { resolve } from "node:path";
|
|
24
|
-
import { palHome } from "../../../../src/hooks/lib/paths";
|
|
25
|
-
|
|
26
|
-
const TELOS_DIR = resolve(palHome(), "telos");
|
|
27
|
-
const BACKUPS_DIR = resolve(TELOS_DIR, "backups");
|
|
28
|
-
const UPDATES_LOG = resolve(TELOS_DIR, "updates.md");
|
|
29
|
-
const PROJECTS_FILE = resolve(TELOS_DIR, "PROJECTS.md");
|
|
30
|
-
|
|
31
|
-
function timestamp(): string {
|
|
32
|
-
const now = new Date();
|
|
33
|
-
const pad = (n: number) => n.toString().padStart(2, "0");
|
|
34
|
-
return `${now.getFullYear()}${pad(now.getMonth() + 1)}${pad(now.getDate())}-${pad(now.getHours())}${pad(now.getMinutes())}${pad(now.getSeconds())}`;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
function isoDate(): string {
|
|
38
|
-
return new Date().toISOString().replace("T", " ").slice(0, 19);
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export interface UpsertProjectResult {
|
|
42
|
-
file: string;
|
|
43
|
-
id: string;
|
|
44
|
-
mode: "replaced" | "appended";
|
|
45
|
-
backed_up: boolean;
|
|
46
|
-
logged: boolean;
|
|
47
|
-
description: string;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
export function upsertProject(
|
|
51
|
-
id: string,
|
|
52
|
-
row: string,
|
|
53
|
-
description: string
|
|
54
|
-
): UpsertProjectResult {
|
|
55
|
-
mkdirSync(BACKUPS_DIR, { recursive: true });
|
|
56
|
-
if (existsSync(PROJECTS_FILE)) {
|
|
57
|
-
const backupName = `PROJECTS-${timestamp()}.md`;
|
|
58
|
-
copyFileSync(PROJECTS_FILE, resolve(BACKUPS_DIR, backupName));
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const existing = existsSync(PROJECTS_FILE) ? readFileSync(PROJECTS_FILE, "utf-8") : "";
|
|
62
|
-
|
|
63
|
-
const escapedId = id.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
64
|
-
const idPattern = new RegExp(`^\\|\\s*${escapedId}\\s*\\|.*$`, "m");
|
|
65
|
-
let mode: "replaced" | "appended";
|
|
66
|
-
|
|
67
|
-
if (idPattern.test(existing)) {
|
|
68
|
-
writeFileSync(PROJECTS_FILE, existing.replace(idPattern, row.trim()), "utf-8");
|
|
69
|
-
mode = "replaced";
|
|
70
|
-
} else {
|
|
71
|
-
const separator = existing.trim() ? "\n" : "";
|
|
72
|
-
writeFileSync(
|
|
73
|
-
PROJECTS_FILE,
|
|
74
|
-
`${existing.trimEnd()}${separator}${row.trim()}\n`,
|
|
75
|
-
"utf-8"
|
|
76
|
-
);
|
|
77
|
-
mode = "appended";
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
const logEntry = `- **${isoDate()}** — \`PROJECTS.md\` [${id}]: ${description}`;
|
|
81
|
-
const existingLog = existsSync(UPDATES_LOG)
|
|
82
|
-
? readFileSync(UPDATES_LOG, "utf-8")
|
|
83
|
-
: "# TELOS Updates\n";
|
|
84
|
-
writeFileSync(UPDATES_LOG, `${existingLog.trimEnd()}\n${logEntry}\n`, "utf-8");
|
|
85
|
-
|
|
86
|
-
return { file: "PROJECTS.md", id, mode, backed_up: true, logged: true, description };
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
function run() {
|
|
90
|
-
const args = process.argv.slice(2);
|
|
91
|
-
const id = args[0];
|
|
92
|
-
const row = args[1];
|
|
93
|
-
const description = args[2];
|
|
94
|
-
|
|
95
|
-
if (!id || !row || !description) {
|
|
96
|
-
console.error('Usage: bun update-projects.ts <id> "<row>" "<description>"');
|
|
97
|
-
console.error(
|
|
98
|
-
'\nExample: bun update-projects.ts my-proj "| my-proj | My Project | In progress | High | Notes |" "Added My Project"'
|
|
99
|
-
);
|
|
100
|
-
process.exit(1);
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
console.log(JSON.stringify(upsertProject(id, row, description), null, 2));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
if (import.meta.main) run();
|