portable-agent-layer 0.78.2 → 0.79.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.
Files changed (36) hide show
  1. package/README.md +3 -2
  2. package/assets/schema/pal-settings.schema.json +0 -1
  3. package/assets/skills/pal-reflect/SKILL.md +2 -2
  4. package/assets/templates/PAL/SYSTEM_ARCHITECTURE.md +4 -4
  5. package/assets/templates/pal-settings.json +0 -1
  6. package/package.json +2 -2
  7. package/src/cli/builtin-tools.ts +1 -0
  8. package/src/cli/index.ts +3 -48
  9. package/src/cli/setup-auto-update.ts +7 -7
  10. package/src/hooks/AutoUpdate.ts +1 -1
  11. package/src/hooks/LoadContext.ts +13 -1
  12. package/src/hooks/PreCompactPersist.ts +2 -2
  13. package/src/hooks/handlers/persist-last-exchange.ts +128 -33
  14. package/src/hooks/handlers/session-intelligence.ts +53 -14
  15. package/src/hooks/lib/algorithm-review.ts +12 -7
  16. package/src/hooks/lib/auto-update.ts +16 -6
  17. package/src/hooks/lib/context.ts +18 -56
  18. package/src/hooks/lib/graduation.ts +2 -2
  19. package/src/hooks/lib/handoff-context.ts +112 -0
  20. package/src/hooks/lib/inference.ts +12 -45
  21. package/src/hooks/lib/log.ts +44 -3
  22. package/src/hooks/lib/paths.ts +7 -2
  23. package/src/hooks/lib/projects.ts +16 -4
  24. package/src/hooks/lib/relationship.ts +2 -2
  25. package/src/hooks/lib/retrieval-index.ts +13 -6
  26. package/src/hooks/lib/security.ts +29 -3
  27. package/src/hooks/lib/semi-static.ts +10 -3
  28. package/src/hooks/lib/session-context.ts +22 -0
  29. package/src/hooks/lib/session-dir.ts +9 -0
  30. package/src/hooks/lib/settings.ts +11 -12
  31. package/src/hooks/lib/work-tracking.ts +9 -1
  32. package/src/tools/{relationship-reflect.ts → agent/relationship-reflect.ts} +13 -12
  33. package/src/tools/control-room/ui/dist/assets/{index-DyhnNmdi.js → index-D_o9Usd_.js} +1 -1
  34. package/src/tools/control-room/ui/dist/index.html +1 -1
  35. package/src/tools/control-room/ui/screens/settings.tsx +2 -2
  36. package/src/tools/self-model.ts +1 -0
@@ -7,10 +7,11 @@
7
7
  */
8
8
 
9
9
  import { existsSync, readFileSync } from "node:fs";
10
- import { resolve } from "node:path";
10
+ import { basename, resolve } from "node:path";
11
11
  import { type FailureEntry, readFailures } from "./learning-store";
12
12
  import { loadOpinionContext } from "./opinions";
13
13
  import { palHome, paths } from "./paths";
14
+ import { type ProjectProgress, readAllProjects, resolveProjectFromCwd } from "./projects";
14
15
  import { readFramePrinciples } from "./wisdom";
15
16
 
16
17
  /** A single semi-static context source — built at session stop, loaded natively at session start. */
@@ -67,6 +68,12 @@ export function rankFailures(
67
68
  .slice(0, limit);
68
69
  }
69
70
 
71
+ function projectTag(cwd: string, projects: ProjectProgress[]): string {
72
+ if (!cwd) return "";
73
+ const name = resolveProjectFromCwd(cwd, projects)?.name ?? basename(cwd);
74
+ return `[${name}]`;
75
+ }
76
+
70
77
  /** Build the failure avoid-list, prioritized by relevance to the current project. */
71
78
  export function loadFailurePatterns(): string {
72
79
  try {
@@ -74,11 +81,11 @@ export function loadFailurePatterns(): string {
74
81
  const all = readFailures(paths.failures());
75
82
  if (all.length === 0) return "";
76
83
 
84
+ const projects = readAllProjects();
77
85
  const lines = rankFailures(all, cwd).map((e) => {
78
86
  const label = e.rating ? `[${e.rating}/10]` : "";
79
- const tag = e.cwd === cwd ? "[project]" : "[other]";
80
87
  const text = e.principle || e.context;
81
- return `- ${label} ${tag} ${text}`.trim();
88
+ return ["-", label, projectTag(e.cwd, projects), text].filter(Boolean).join(" ");
82
89
  });
83
90
 
84
91
  return ["## Lessons from Recent Failures — Apply These Now", ...lines].join("\n");
@@ -24,6 +24,28 @@ export function isSubagentSession(env: NodeJS.ProcessEnv): boolean {
24
24
  return env.CLAUDE_PROJECT_DIR?.includes("/.claude/Agents/") ?? false;
25
25
  }
26
26
 
27
+ export type SessionStartContext = "full" | "without-handoff" | "none";
28
+
29
+ /**
30
+ * Claude Code and Codex replay the context injected at startup when a session
31
+ * is resumed. Copilot does not document it, and has shipped a release that
32
+ * dropped startup context, so it keeps getting the full context on resume.
33
+ */
34
+ const RESUME_KEEPS_CONTEXT: ReadonlySet<string> = new Set(["claude", "codex"]);
35
+
36
+ /**
37
+ * After a compaction, CompactRecover restores the last exchange, so the
38
+ * handoff is redundant. Agents that name no source get everything.
39
+ */
40
+ export function contextForSource(
41
+ agent: string,
42
+ source: string | undefined
43
+ ): SessionStartContext {
44
+ if (source === "resume" && RESUME_KEEPS_CONTEXT.has(agent)) return "none";
45
+ if (source === "compact") return "without-handoff";
46
+ return "full";
47
+ }
48
+
27
49
  /**
28
50
  * Copilot and Cursor read AGENTS.md natively from nothing, so it is prepended
29
51
  * here; Codex reaches it through a symlink and Claude Code loads it itself.
@@ -0,0 +1,9 @@
1
+ /**
2
+ * The folder a session was started in. A hook's own cwd follows every `cd`
3
+ * the agent makes, so keying by it files a session under whatever subfolder
4
+ * it happened to end in. Claude Code exports the start folder as
5
+ * CLAUDE_PROJECT_DIR; agents that do not fall back to the cwd.
6
+ */
7
+ export function sessionDir(env: NodeJS.ProcessEnv = process.env): string {
8
+ return env.CLAUDE_PROJECT_DIR || process.cwd();
9
+ }
@@ -54,28 +54,27 @@ const IDENTITY_DEFAULTS: Identity = {
54
54
 
55
55
  // ── Singleton ──
56
56
 
57
- let cached: PalSettingsData | null = null;
57
+ let cached: { path: string; data: PalSettingsData } | null = null;
58
58
 
59
59
  function settingsPath(): string {
60
60
  return resolve(paths.memory(), "pal-settings.json");
61
61
  }
62
62
 
63
- function load(): PalSettingsData {
64
- if (cached) return cached;
65
- const p = settingsPath();
66
- if (!existsSync(p)) {
67
- cached = {};
68
- return cached;
69
- }
63
+ function readSettingsFile(p: string): PalSettingsData {
64
+ if (!existsSync(p)) return {};
70
65
  try {
71
- cached = JSON.parse(readFileSync(p, "utf-8")) as PalSettingsData;
72
- return cached;
66
+ return JSON.parse(readFileSync(p, "utf-8")) as PalSettingsData;
73
67
  } catch {
74
- cached = {};
75
- return cached;
68
+ return {};
76
69
  }
77
70
  }
78
71
 
72
+ function load(): PalSettingsData {
73
+ const p = settingsPath();
74
+ if (cached?.path !== p) cached = { path: p, data: readSettingsFile(p) };
75
+ return cached.data;
76
+ }
77
+
79
78
  /** Force re-read from disk (useful after writes) */
80
79
  export function reload(): PalSettingsData {
81
80
  cached = null;
@@ -169,7 +169,15 @@ export function appendProjectHistory(cwd: string, entry: ProjectHistoryEntry): v
169
169
 
170
170
  /** Read the session history for a given cwd */
171
171
  export function readProjectHistory(cwd: string, limit = 15): ProjectHistoryEntry[] {
172
- const historyPath = historyFileFor(cwd);
172
+ return readHistoryFile(historyFileFor(cwd), limit);
173
+ }
174
+
175
+ export function lastSessionTitle(project: string): string | undefined {
176
+ const historyPath = resolve(paths.projectHistory(), project, "history.jsonl");
177
+ return readHistoryFile(historyPath, 1)[0]?.title;
178
+ }
179
+
180
+ function readHistoryFile(historyPath: string, limit: number): ProjectHistoryEntry[] {
173
181
  if (!existsSync(historyPath)) return [];
174
182
  try {
175
183
  const lines = readFileSync(historyPath, "utf-8").trim().split("\n").filter(Boolean);
@@ -8,17 +8,17 @@
8
8
  * - Generate a summary report
9
9
  *
10
10
  * Usage:
11
- * bun run tool:reflect # Reflect on last 7 days
12
- * bun run tool:reflect -- --month # Reflect on last 30 days
13
- * bun run tool:reflect -- --dry-run # Preview without writing
11
+ * pal cli relationship-reflect # Reflect on last 7 days
12
+ * pal cli relationship-reflect --month # Reflect on last 30 days
13
+ * pal cli relationship-reflect --dry-run # Preview without writing
14
14
  */
15
15
 
16
16
  import { existsSync, mkdirSync, writeFileSync } from "node:fs";
17
17
  import { resolve } from "node:path";
18
18
  import { parseArgs } from "node:util";
19
- import { readOpinions, saveOpinion, setLastReflectDate } from "../hooks/lib/opinions";
20
- import { palHome } from "../hooks/lib/paths";
21
- import { emit } from "./lib/emit";
19
+ import { readOpinions, saveOpinion, setLastReflectDate } from "../../hooks/lib/opinions";
20
+ import { palHome } from "../../hooks/lib/paths";
21
+ import { emit } from "../lib/emit";
22
22
  import {
23
23
  consoleLines,
24
24
  formatReport,
@@ -27,7 +27,8 @@ import {
27
27
  loadRatings,
28
28
  planPromotions,
29
29
  reportPath,
30
- } from "./lib/relationship-reflect";
30
+ } from "../lib/relationship-reflect";
31
+ import { scriptArgs } from "../lib/script-args";
31
32
 
32
33
  const HELP = `
33
34
  RelationshipReflect — Periodic reflection + opinion promotion
@@ -36,9 +37,9 @@ Reads recent relationship notes and ratings. Promotes recurring
36
37
  observations (O type) into tracked opinions with confidence scoring.
37
38
 
38
39
  Usage:
39
- bun run tool:reflect Reflect on last 7 days (default)
40
- bun run tool:reflect -- --month Reflect on last 30 days
41
- bun run tool:reflect -- --dry-run Preview without writing
40
+ pal cli relationship-reflect Reflect on last 7 days (default)
41
+ pal cli relationship-reflect --month Reflect on last 30 days
42
+ pal cli relationship-reflect --dry-run Preview without writing
42
43
 
43
44
  Output:
44
45
  - Updates memory/relationship/opinions.json (confidence tracking)
@@ -56,9 +57,9 @@ function saveReport(report: string, period: string): string {
56
57
  return filepath;
57
58
  }
58
59
 
59
- function run() {
60
+ export function run(argv: string[] = scriptArgs()) {
60
61
  const { values } = parseArgs({
61
- args: Bun.argv.slice(2),
62
+ args: argv,
62
63
  options: {
63
64
  month: { type: "boolean" },
64
65
  "dry-run": { type: "boolean" },