portable-agent-layer 0.85.13 → 0.85.14

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.85.13",
3
+ "version": "0.85.14",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -11,29 +11,49 @@ interface PendingMigration {
11
11
  detail?: string;
12
12
  }
13
13
 
14
+ interface ErrorContext {
15
+ now: number;
16
+ env: Record<string, string | undefined>;
17
+ }
18
+
14
19
  const CLAUDE_LOGIN_EXPIRED = /Failed to authenticate/;
20
+ const MINUTE_MS = 60_000;
15
21
 
16
- function claudeLoginExpiredFinding(group: HookErrorGroup): Finding {
17
- return failing(
18
- `hook-errors.${group.source}`,
19
- `Background calls to Claude cannot log in — ${group.count} failed in the last 24h: ${group.last}`,
20
- {
21
- say: "Create a year-long token, then export it as CLAUDE_CODE_OAUTH_TOKEN in your shell profile",
22
+ function ago(at: number, now: number): string {
23
+ const minutes = Math.max(0, Math.round((now - at) / MINUTE_MS));
24
+ return minutes < 60 ? `${minutes}m ago` : `${Math.floor(minutes / 60)}h ago`;
25
+ }
26
+
27
+ function claudeLoginExpiredFinding(
28
+ group: HookErrorGroup,
29
+ context: ErrorContext
30
+ ): Finding {
31
+ const title = `Background calls to Claude could not log in — ${group.count} failed in the last 24h, last ${ago(group.lastAt, context.now)}: ${group.last}`;
32
+ if (context.env.CLAUDE_CODE_OAUTH_TOKEN)
33
+ return warning(`hook-errors.${group.source}`, title, {
34
+ say: "CLAUDE_CODE_OAUTH_TOKEN is set — if these failures predate it, they clear 24h after the last one; if not, the token is invalid, so create a new one",
22
35
  command: "claude setup-token",
23
- }
24
- );
36
+ });
37
+ return failing(`hook-errors.${group.source}`, title, {
38
+ say: "Create a year-long token, then export it as CLAUDE_CODE_OAUTH_TOKEN in your shell profile",
39
+ command: "claude setup-token",
40
+ });
25
41
  }
26
42
 
27
- export function hookErrorFindings(groups: HookErrorGroup[]): Finding[] {
43
+ export function hookErrorFindings(
44
+ groups: HookErrorGroup[],
45
+ context: ErrorContext = { now: Date.now(), env: process.env }
46
+ ): Finding[] {
28
47
  if (groups.length === 0)
29
48
  return [passed("hook-errors", "No hook errors in the last 24h")];
30
49
  const log = resolve(palHome(), "debug", "debug.log");
31
50
  return groups.map((group) => {
32
- if (CLAUDE_LOGIN_EXPIRED.test(group.last)) return claudeLoginExpiredFinding(group);
51
+ if (CLAUDE_LOGIN_EXPIRED.test(group.last))
52
+ return claudeLoginExpiredFinding(group, context);
33
53
  const times = group.count === 1 ? "once" : `${group.count} times`;
34
54
  return warning(
35
55
  `hook-errors.${group.source}`,
36
- `${group.source} failed ${times} in the last 24h — last: ${group.last}`,
56
+ `${group.source} failed ${times} in the last 24h, last ${ago(group.lastAt, context.now)}: ${group.last}`,
37
57
  { say: `Full trace in ${log}` }
38
58
  );
39
59
  });
@@ -3,6 +3,7 @@ import { resolve } from "node:path";
3
3
  import { palHome, palPkg } from "../../hooks/lib/paths";
4
4
  import { auditBindings, type BindingIssue } from "../../hooks/lib/projects";
5
5
  import { telosStatus } from "../../hooks/lib/telos-topics";
6
+ import { retiredKeysIn } from "../migrate";
6
7
  import { type Finding, failing, optional, passed, warning } from "./finding";
7
8
 
8
9
  interface SchemaNode {
@@ -142,7 +143,12 @@ function settingsFindings(): Finding[] {
142
143
  }),
143
144
  ];
144
145
  const schema = readSettingsSchema();
145
- const unknown = schema ? unknownSettingsKeys(settings, schema) : [];
146
+ const leftToMigration = new Set(
147
+ retiredKeysIn(settings as Record<string, Record<string, unknown>>)
148
+ );
149
+ const unknown = schema
150
+ ? unknownSettingsKeys(settings, schema).filter((k) => !leftToMigration.has(k.path))
151
+ : [];
146
152
  return [identityFinding(settings), unknownKeysFinding(unknown)];
147
153
  }
148
154
 
@@ -671,7 +671,9 @@ function readSettingsObject(): Record<string, Record<string, unknown>> | null {
671
671
  }
672
672
  }
673
673
 
674
- function retiredKeysIn(settings: Record<string, Record<string, unknown>>): string[] {
674
+ export function retiredKeysIn(
675
+ settings: Record<string, Record<string, unknown>>
676
+ ): string[] {
675
677
  return Object.entries(RETIRED_SETTINGS_KEYS).flatMap(([section, keys]) => {
676
678
  const value = settings[section];
677
679
  if (typeof value !== "object" || value === null) return [];
@@ -83,6 +83,12 @@ export interface HookErrorGroup {
83
83
  source: string;
84
84
  count: number;
85
85
  last: string;
86
+ lastAt: number;
87
+ }
88
+
89
+ interface LoggedError {
90
+ at: number;
91
+ message: string;
86
92
  }
87
93
 
88
94
  const DAY_MS = 24 * 60 * 60 * 1000;
@@ -114,13 +120,14 @@ function whatTheAgentSaid(message: string): string {
114
120
  return output[1].trim() || output[2].trim() || message;
115
121
  }
116
122
 
117
- function groupBySource(errors: string[]): HookErrorGroup[] {
123
+ function groupBySource(errors: LoggedError[]): HookErrorGroup[] {
118
124
  const groups = new Map<string, HookErrorGroup>();
119
- for (const error of errors) {
120
- const source = /^([^:]+):/.exec(error)?.[1] ?? "unknown";
121
- const group = groups.get(source) ?? { source, count: 0, last: "" };
125
+ for (const { at, message } of errors) {
126
+ const source = /^([^:]+):/.exec(message)?.[1] ?? "unknown";
127
+ const group = groups.get(source) ?? { source, count: 0, last: "", lastAt: 0 };
122
128
  group.count++;
123
- group.last = whatTheAgentSaid(error.slice(source.length + 1).trim()).slice(0, 120);
129
+ group.last = whatTheAgentSaid(message.slice(source.length + 1).trim()).slice(0, 120);
130
+ group.lastAt = at;
124
131
  groups.set(source, group);
125
132
  }
126
133
  return [...groups.values()].sort((a, b) => b.count - a.count);
@@ -131,7 +138,10 @@ export function recentHookErrors(now: number = Date.now()): HookErrorGroup[] {
131
138
  const recent = readAllLogs()
132
139
  .split("\n")
133
140
  .filter((line) => line.includes("] ERROR ") && loggedAt(line) > now - DAY_MS)
134
- .map((line) => line.replace(/^\[.*?\] ERROR /, ""));
141
+ .map((line) => ({
142
+ at: loggedAt(line),
143
+ message: line.replace(/^\[.*?\] ERROR /, ""),
144
+ }));
135
145
  return groupBySource(recent);
136
146
  } catch {
137
147
  return [];