portable-agent-layer 0.83.0 → 0.84.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.83.0",
3
+ "version": "0.84.1",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/index.ts CHANGED
@@ -110,6 +110,16 @@ function checkCopilot(): ToolCheck {
110
110
  return cli;
111
111
  }
112
112
 
113
+ /**
114
+ * The Cursor CLI installs `cursor-agent` (and `agent`) and no `cursor` command;
115
+ * `cursor` is the editor's shell command. Both read ~/.cursor, so either counts.
116
+ */
117
+ function checkCursor(): ToolCheck {
118
+ const cli = checkTool("cursor-agent");
119
+ if (cli.available) return cli;
120
+ return checkTool("cursor");
121
+ }
122
+
113
123
  async function session(sessionArgs: string[]) {
114
124
  const agent = findSessionAgent();
115
125
  if (!agent) {
@@ -759,7 +769,7 @@ function doctor(silent = false): DoctorResult {
759
769
  const bun = { name: "bun", available: true, version: Bun.version };
760
770
  const claude = checkTool("claude");
761
771
  const opencode = checkTool("opencode");
762
- const cursor = checkTool("cursor");
772
+ const cursor = checkCursor();
763
773
  const copilot = checkCopilot();
764
774
  const codex = checkTool("codex");
765
775
  const rtk = checkTool("rtk");
@@ -51,6 +51,7 @@ export interface TurnEvent extends MoodTurn {
51
51
  reaction: Reaction | null;
52
52
  mood?: string;
53
53
  hinted?: boolean;
54
+ runtime?: string;
54
55
  }
55
56
 
56
57
  interface SessionTrack {
@@ -15,7 +15,8 @@ const HELP = `
15
15
  PAL Interaction report
16
16
 
17
17
  Reads the measured turns in memory/signals/interaction/ (features only, no
18
- text) and prints turns, channels, reactions, the hints sent, and for each
18
+ text) and prints turns, channels, reactions, the hints sent, per agent the
19
+ turns, replies filed and hints sent, and for each
19
20
  label (short, long, fast, skimming, friction) how the replies written while
20
21
  it was active compare with the replies written while none was.
21
22
 
@@ -22,10 +22,17 @@ interface ReplyStats {
22
22
  correctedShare: number;
23
23
  }
24
24
 
25
+ interface AgentCounts {
26
+ turns: number;
27
+ replies: number;
28
+ hints: number;
29
+ }
30
+
25
31
  export interface InteractionSummary {
26
32
  turns: number;
27
33
  sessions: number;
28
34
  channels: Record<string, number>;
35
+ agents: Record<string, AgentCounts>;
29
36
  reactions: Record<string, number>;
30
37
  hints: Record<string, number>;
31
38
  unlogged: number;
@@ -70,6 +77,20 @@ function repliesUnderMood(events: TurnEvent[]): ReplyUnderMood[] {
70
77
  return replies;
71
78
  }
72
79
 
80
+ /** A turn carries the reply that answered the turn before it, so a filed reply shows up there. */
81
+ function byAgent(events: TurnEvent[]): Record<string, AgentCounts> {
82
+ const agents: Record<string, AgentCounts> = {};
83
+ for (const e of events) {
84
+ const agent = e.runtime ?? "unknown";
85
+ const counts = agents[agent] ?? { turns: 0, replies: 0, hints: 0 };
86
+ agents[agent] = counts;
87
+ counts.turns++;
88
+ if (e.reply) counts.replies++;
89
+ if (e.hinted) counts.hints++;
90
+ }
91
+ return agents;
92
+ }
93
+
73
94
  export function summarize(events: TurnEvent[]): InteractionSummary {
74
95
  const replies = repliesUnderMood(events);
75
96
  const hinted = events.filter((e) => e.hinted);
@@ -77,6 +98,7 @@ export function summarize(events: TurnEvent[]): InteractionSummary {
77
98
  turns: events.length,
78
99
  sessions: new Set(events.map((e) => e.session)).size,
79
100
  channels: tally(events.map((e) => e.channel ?? "unknown")),
101
+ agents: byAgent(events),
80
102
  reactions: tally(events.flatMap((e) => (e.reaction ? [e.reaction] : []))),
81
103
  hints: tally(hinted.map((e) => e.mood || "back to usual")),
82
104
  unlogged: events.filter((e) => e.mood === undefined).length,
@@ -119,8 +141,20 @@ function labelLine(label: string, s: ReplyStats, usual: ReplyStats): string {
119
141
  ].join(" · ");
120
142
  }
121
143
 
122
- function plural(n: number, noun: string): string {
123
- return `${n} ${noun}${n === 1 ? "" : "s"}`;
144
+ function plural(n: number, noun: string, nouns = `${noun}s`): string {
145
+ return `${n} ${n === 1 ? noun : nouns}`;
146
+ }
147
+
148
+ function agentLines(agents: Record<string, AgentCounts>): string[] {
149
+ return Object.entries(agents)
150
+ .sort(([, a], [, b]) => b.turns - a.turns)
151
+ .map(([agent, c]) =>
152
+ [
153
+ ` ${agent.padEnd(9)} ${plural(c.turns, "turn")}`,
154
+ `${plural(c.replies, "reply", "replies")} filed`,
155
+ `${plural(c.hints, "hint")} sent`,
156
+ ].join(" · ")
157
+ );
124
158
  }
125
159
 
126
160
  export function reportLines(summary: InteractionSummary, days: number): string[] {
@@ -132,6 +166,9 @@ export function reportLines(summary: InteractionSummary, days: number): string[]
132
166
  `Reactions: ${shares(summary.reactions)}`,
133
167
  `Hints sent: ${counts(summary.hints)}`,
134
168
  "",
169
+ "By agent:",
170
+ ...agentLines(summary.agents),
171
+ "",
135
172
  "Replies written while a label was active, against replies while none was:",
136
173
  ...LABELS.map((label) => labelLine(label, summary.byLabel[label], summary.usual)),
137
174
  ];