portable-agent-layer 0.61.3 → 0.62.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 (34) hide show
  1. package/assets/schema/pal-settings.schema.json +111 -0
  2. package/assets/skills/opinion/SKILL.md +7 -7
  3. package/assets/skills/opinion/tools/opinion.ts +6 -4
  4. package/assets/templates/hooks.copilot.json +4 -0
  5. package/assets/templates/hooks.cursor.json +4 -0
  6. package/assets/templates/pal-settings.json +8 -3
  7. package/assets/templates/settings.claude.json +9 -2
  8. package/package.json +1 -1
  9. package/src/cli/index.ts +16 -1
  10. package/src/hooks/RtkWrap.ts +52 -0
  11. package/src/hooks/UserPromptOrchestrator.ts +3 -3
  12. package/src/hooks/handlers/failure-principle.ts +1 -1
  13. package/src/hooks/handlers/inject-retrieval.ts +18 -6
  14. package/src/hooks/handlers/rating.ts +1 -1
  15. package/src/hooks/handlers/session-intelligence.ts +1 -1
  16. package/src/hooks/handlers/session-name.ts +1 -1
  17. package/src/hooks/handlers/update-check.ts +8 -5
  18. package/src/hooks/lib/inference.ts +2 -41
  19. package/src/hooks/lib/opinions.ts +15 -4
  20. package/src/hooks/lib/settings.ts +5 -0
  21. package/src/hooks/lib/steering.ts +162 -0
  22. package/src/hooks/lib/text-similarity.ts +18 -0
  23. package/src/hooks/lib/which.ts +41 -0
  24. package/src/targets/lib.ts +18 -1
  25. package/src/targets/opencode/plugin.ts +35 -4
  26. package/src/tools/agent/algorithm-reflect.ts +2 -1
  27. package/src/tools/agent/handoff-note.ts +3 -2
  28. package/src/tools/agent/project.ts +42 -2
  29. package/src/tools/agent/relationship-note.ts +6 -11
  30. package/src/tools/agent/thread.ts +6 -9
  31. package/src/tools/agent/wisdom-frame.ts +2 -1
  32. package/src/tools/lib/emit.ts +31 -0
  33. package/src/tools/relationship-reflect.ts +14 -13
  34. package/src/tools/token-cost.ts +49 -0
@@ -26,6 +26,7 @@ import {
26
26
  } from "../hooks/lib/opinions";
27
27
  import { palHome } from "../hooks/lib/paths";
28
28
  import { similarity } from "../hooks/lib/text-similarity";
29
+ import { emit } from "./lib/emit";
29
30
 
30
31
  // ── Types ──
31
32
 
@@ -413,11 +414,11 @@ Output:
413
414
  const notes = loadNotes(daysBack);
414
415
  const ratings = loadRatings(daysBack);
415
416
 
416
- console.log(`Loaded ${notes.length} notes from last ${daysBack} days`);
417
- console.log(`Loaded ${ratings.length} ratings`);
417
+ emit.ok(`Loaded ${notes.length} notes from last ${daysBack} days`);
418
+ emit.ok(`Loaded ${ratings.length} ratings`);
418
419
 
419
420
  if (notes.length === 0 && ratings.length === 0) {
420
- console.log("No data to analyze");
421
+ emit.ok("No data to analyze");
421
422
  process.exit(0);
422
423
  }
423
424
 
@@ -425,42 +426,42 @@ Output:
425
426
 
426
427
  const avgRating =
427
428
  ratings.length > 0 ? ratings.reduce((s, r) => s + r.rating, 0) / ratings.length : 0;
428
- console.log(`\nAverage Rating: ${avgRating.toFixed(1)}/10`);
429
+ emit.ok(`\nAverage Rating: ${avgRating.toFixed(1)}/10`);
429
430
 
430
431
  const summaries = groupNoteOccurrences(notes);
431
- console.log(`Observations: ${summaries.length} unique`);
432
+ emit.ok(`Observations: ${summaries.length} unique`);
432
433
 
433
434
  if (opinionChanges.length > 0) {
434
- console.log("\nOpinion changes:");
435
+ emit.ok("\nOpinion changes:");
435
436
  for (const change of opinionChanges) {
436
437
  if (change.action === "created") {
437
- console.log(
438
+ emit.ok(
438
439
  ` + NEW (${Math.round(change.newConfidence * 100)}%) ${change.statement.slice(0, 80)}`
439
440
  );
440
441
  } else {
441
- console.log(
442
+ emit.ok(
442
443
  ` ~ ${Math.round(change.oldConfidence ?? 0 * 100)}% → ${Math.round(change.newConfidence * 100)}% ${change.statement.slice(0, 80)}`
443
444
  );
444
445
  }
445
446
  }
446
447
  } else {
447
- console.log("\nNo opinion changes");
448
+ emit.ok("\nNo opinion changes");
448
449
  }
449
450
 
450
451
  if (dryRun) {
451
- console.log("\n[DRY RUN] Would write reflection report + update opinions");
452
+ emit.data("[DRY RUN] Would write reflection report + update opinions");
452
453
  } else {
453
454
  const report = formatReport(period, notes, ratings, opinionChanges);
454
455
  const filepath = writeReport(report, period);
455
456
  setLastReflectDate(new Date().toISOString().slice(0, 10));
456
- console.log(`\nCreated reflection report: ${filepath}`);
457
+ emit.ok(`\nCreated reflection report: ${filepath}`);
457
458
 
458
459
  const opinions = readOpinions();
459
460
  const high = opinions.filter((o) => o.confidence >= 0.85);
460
461
  if (high.length > 0) {
461
- console.log("\nHigh-confidence opinions (injected into context):");
462
+ emit.ok("\nHigh-confidence opinions (injected into context):");
462
463
  for (const o of high) {
463
- console.log(` [${Math.round(o.confidence * 100)}%] ${o.statement.slice(0, 80)}`);
464
+ emit.ok(` [${Math.round(o.confidence * 100)}%] ${o.statement.slice(0, 80)}`);
464
465
  }
465
466
  }
466
467
  }
@@ -8,12 +8,14 @@
8
8
  * Invoked via `pal cli usage [--today|--week|--month|--all] [--project <name>]`.
9
9
  */
10
10
 
11
+ import { spawnSync } from "node:child_process";
11
12
  import { existsSync, readdirSync, readFileSync } from "node:fs";
12
13
  import { homedir } from "node:os";
13
14
  import { resolve } from "node:path";
14
15
  import { parseArgs } from "node:util";
15
16
  import { MODEL_PRICING } from "../hooks/lib/models";
16
17
  import { palHome } from "../hooks/lib/paths";
18
+ import { findBinaryOnPath } from "../hooks/lib/which";
17
19
 
18
20
  // ── Types ──
19
21
 
@@ -370,6 +372,51 @@ function readPalInference(): {
370
372
  return { buckets, byModel, byCaller };
371
373
  }
372
374
 
375
+ // ── rtk compression savings ──
376
+
377
+ interface RtkSummary {
378
+ total_commands: number;
379
+ total_saved: number;
380
+ avg_savings_pct: number;
381
+ }
382
+
383
+ /**
384
+ * Query rtk's own savings ledger. `installed: false` means rtk isn't on PATH;
385
+ * `summary: null` with `installed: true` means rtk is present but has no data
386
+ * (or errored) — the two cases print differently in the usage report.
387
+ */
388
+ function readRtkGain(): { installed: boolean; summary: RtkSummary | null } {
389
+ const rtk = findBinaryOnPath("rtk");
390
+ if (!rtk) return { installed: false, summary: null };
391
+ try {
392
+ const r = spawnSync(rtk, ["gain", "--format", "json"], {
393
+ encoding: "utf8",
394
+ stdio: ["ignore", "pipe", "ignore"],
395
+ });
396
+ if (r.status !== 0 || !r.stdout) return { installed: true, summary: null };
397
+ const parsed = JSON.parse(r.stdout) as { summary?: RtkSummary };
398
+ return { installed: true, summary: parsed.summary ?? null };
399
+ } catch {
400
+ return { installed: true, summary: null };
401
+ }
402
+ }
403
+
404
+ function printRtkGain(): void {
405
+ const { installed, summary } = readRtkGain();
406
+ console.log("\n rtk Compression\n");
407
+ if (!installed) {
408
+ console.log(" rtk not installed");
409
+ return;
410
+ }
411
+ if (!summary || summary.total_commands === 0) {
412
+ console.log(" rtk installed — no savings recorded yet");
413
+ return;
414
+ }
415
+ console.log(
416
+ ` Tokens saved ${fmt(summary.total_saved).padStart(8)} tok ${summary.avg_savings_pct.toFixed(1)}% avg across ${fmt(summary.total_commands)} commands`
417
+ );
418
+ }
419
+
373
420
  // ── CLI ──
374
421
 
375
422
  export function usage() {
@@ -426,6 +473,8 @@ export function usage() {
426
473
  printRow("Total", tb.total);
427
474
  }
428
475
 
476
+ printRtkGain();
477
+
429
478
  const grand = emptyBucket();
430
479
  for (const b of [cc.buckets.total, pal.buckets.total]) {
431
480
  grand.input += b.input;