portable-agent-layer 0.85.2 → 0.85.3

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.
@@ -70,6 +70,10 @@
70
70
  "type": "boolean",
71
71
  "description": "One line of the current time with every prompt, in identity.principal.timezone, so a session resumed hours later knows the day changed."
72
72
  },
73
+ "dueReminders": {
74
+ "type": "boolean",
75
+ "description": "Opt-in, off by default until 1.0. Due maintenance reminders (learning analysis, relationship reflect) with every prompt until a reply names their command; then quiet until the next day."
76
+ },
73
77
  "interactionAwareness": {
74
78
  "type": "boolean",
75
79
  "description": "Measure each user turn (pace, message length, skimming, corrections; never the text) into memory/signals/interaction/, and tell the agent when the session's pattern changes."
@@ -26,6 +26,7 @@
26
26
  "contextualSteering": true,
27
27
  "skillMatching": true,
28
28
  "wallClock": true,
29
+ "dueReminders": false,
29
30
  "interactionAwareness": true,
30
31
  "reactionSampling": true,
31
32
  "steeringTestReport": false
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "portable-agent-layer",
3
- "version": "0.85.2",
3
+ "version": "0.85.3",
4
4
  "description": "PAL — Portable Agent Layer: persistent personal context for AI coding assistants",
5
5
  "type": "module",
6
6
  "bin": {
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import { promptContextResponse } from "../lib/agent";
11
+ import { dueNudgeReminder } from "../lib/daily-nudge";
11
12
  import { observeTurn } from "../lib/interaction";
12
13
  import { logDebug, logError } from "../lib/log";
13
14
  import { parkPromptContext } from "../lib/parked-context";
@@ -81,6 +82,7 @@ export async function getPromptContext(
81
82
  const parts = [
82
83
  getWallClockReminder(),
83
84
  withinBudget(() => observeTurn(prompt, sessionId), BUDGET_MS),
85
+ withinBudget(() => dueNudgeReminder(), BUDGET_MS),
84
86
  getSteeringReminder(prompt),
85
87
  getSkillReminder(prompt),
86
88
  await getRetrievalReminder(prompt),
@@ -5,16 +5,12 @@
5
5
 
6
6
  import { existsSync, readFileSync } from "node:fs";
7
7
  import { resolve } from "node:path";
8
- import { loadReflectNudge } from "../handlers/reflect-trigger";
9
- import { loadAlgorithmReviewNudge } from "./algorithm-review";
10
- import { loadAnalyzeNudge } from "./analyze-nudge";
11
8
  import { resolveAnchor } from "./anchor";
12
- import { oncePerDay } from "./daily-nudge";
9
+ import { dueNudgeSections } from "./daily-nudge";
13
10
  import { loadHandoffContext } from "./handoff-context";
14
11
  import { loadOpinionContext } from "./opinions";
15
12
  import { paths, toPath } from "./paths";
16
13
  import { loadActiveProjectsContext } from "./projects";
17
- import { loadReactionAuditNudge } from "./reaction-audit";
18
14
  import { loadRecentNotes } from "./relationship";
19
15
  import { loadFailurePatterns } from "./semi-static";
20
16
  import { sessionDir } from "./session-dir";
@@ -219,18 +215,11 @@ export function buildSystemReminder(
219
215
  !opts.withoutHandoff && settings.isEnabled("handoff")
220
216
  ? loadHandoffContext(sessionDir())
221
217
  : "";
222
- // Maintainer-only: self-gates to a repo checkout, "" for everyone else.
223
- const algoReview = oncePerDay("algorithm-review", loadAlgorithmReviewNudge());
224
- const reactionAudit = oncePerDay("reaction-audit", loadReactionAuditNudge());
225
- const reflectNudge = oncePerDay("reflect", loadReflectNudge());
226
- const analyzeNudge = oncePerDay("analyze", loadAnalyzeNudge());
218
+ const dueNudges = opts.agent === "cursor" ? dueNudgeSections() : [];
227
219
  const parts: string[] = [];
228
220
  if (startup) parts.push(startup);
229
221
  if (handoff) parts.push(handoff);
230
- if (algoReview) parts.push(algoReview);
231
- if (reactionAudit) parts.push(reactionAudit);
232
- if (reflectNudge) parts.push(reflectNudge);
233
- if (analyzeNudge) parts.push(analyzeNudge);
222
+ parts.push(...dueNudges);
234
223
  if (selfModel) parts.push(selfModel);
235
224
  if (wisdom) parts.push(wisdom);
236
225
  if (opinions) parts.push(opinions);
@@ -1,10 +1,32 @@
1
1
  import { readFileSync, writeFileSync } from "node:fs";
2
2
  import { resolve } from "node:path";
3
+ import { loadReflectNudge } from "../handlers/reflect-trigger";
4
+ import { loadAlgorithmReviewNudge } from "./algorithm-review";
5
+ import { loadAnalyzeNudge } from "./analyze-nudge";
3
6
  import { ensureDir, paths } from "./paths";
7
+ import { loadReactionAuditNudge } from "./reaction-audit";
8
+ import { isOptedIn } from "./settings";
4
9
  import { localDay } from "./wall-clock";
5
10
 
6
11
  type ShownOn = Record<string, string>;
7
12
 
13
+ interface DueNudge {
14
+ key: string;
15
+ command: string;
16
+ load: () => string;
17
+ }
18
+
19
+ const DUE_NUDGES: DueNudge[] = [
20
+ {
21
+ key: "algorithm-review",
22
+ command: "/algorithm-update",
23
+ load: loadAlgorithmReviewNudge,
24
+ },
25
+ { key: "reaction-audit", command: "/reaction-audit", load: loadReactionAuditNudge },
26
+ { key: "reflect", command: "/pal-reflect", load: loadReflectNudge },
27
+ { key: "analyze", command: "/pal-analyze", load: loadAnalyzeNudge },
28
+ ];
29
+
8
30
  function shownPath(): string {
9
31
  return resolve(ensureDir(paths.state()), "nudges-shown.json");
10
32
  }
@@ -18,18 +40,39 @@ function readShownOn(): ShownOn {
18
40
  }
19
41
 
20
42
  /**
21
- * A reminder asks the user for something, so it belongs in the first session
22
- * of the day, not in every session, resume and compaction after it.
43
+ * A reminder asks the user for something, so it stays until a reply has passed
44
+ * it on, then rests for the rest of the day.
23
45
  */
24
- export function oncePerDay(
46
+ export function pendingToday(
25
47
  key: string,
26
48
  reminder: string,
27
49
  now: Date = new Date()
28
50
  ): string {
29
51
  if (!reminder) return "";
52
+ return readShownOn()[key] === localDay(now) ? "" : reminder;
53
+ }
54
+
55
+ export function acknowledgeMentioned(reply: string, now: Date = new Date()): void {
56
+ const mentioned = DUE_NUDGES.filter((nudge) => reply.includes(nudge.command));
57
+ if (!mentioned.length) return;
30
58
  const today = localDay(now);
31
59
  const shownOn = readShownOn();
32
- if (shownOn[key] === today) return "";
33
- writeFileSync(shownPath(), JSON.stringify({ ...shownOn, [key]: today }), "utf-8");
34
- return reminder;
60
+ for (const nudge of mentioned) shownOn[nudge.key] = today;
61
+ writeFileSync(shownPath(), JSON.stringify(shownOn), "utf-8");
62
+ }
63
+
64
+ export function dueNudgeSections(now: Date = new Date()): string[] {
65
+ if (!isOptedIn("dueReminders")) return [];
66
+ return DUE_NUDGES.map((nudge) => pendingToday(nudge.key, nudge.load(), now)).filter(
67
+ Boolean
68
+ );
69
+ }
70
+
71
+ export function dueNudgeReminder(now: Date = new Date()): string | null {
72
+ const sections = dueNudgeSections(now);
73
+ if (!sections.length) return null;
74
+ return [
75
+ "Tell the user in one line at the end of this reply that the following is due, naming its command. This comes back every turn until a reply does.",
76
+ ...sections,
77
+ ].join("\n\n");
35
78
  }
@@ -115,6 +115,11 @@ export function isEnabled(key: string): boolean {
115
115
  return load().dynamicContext?.[key] !== false;
116
116
  }
117
117
 
118
+ /** Check if an opt-in dynamic context section is enabled (defaults to false) */
119
+ export function isOptedIn(key: string): boolean {
120
+ return load().dynamicContext?.[key] === true;
121
+ }
122
+
118
123
  /** Get the loadAtStartup file list */
119
124
  export function startupFiles(): string[] {
120
125
  return load().loadAtStartup?.files ?? [];
@@ -19,6 +19,7 @@ import { runSynthesis } from "../handlers/synthesis";
19
19
  import { resetTab } from "../handlers/tab";
20
20
  import { updateCounts } from "../handlers/update-counts";
21
21
  import { captureWorkSession } from "../handlers/work-session";
22
+ import { acknowledgeMentioned } from "./daily-nudge";
22
23
  import { spawnDetachedInference } from "./detached-inference";
23
24
  import {
24
25
  fileFinalReply,
@@ -274,7 +275,9 @@ function recordFinalReply(
274
275
  sessionId: string
275
276
  ): void {
276
277
  try {
277
- recordReply(sessionId, extractContent(extractLastAssistant(messages)));
278
+ const reply = extractContent(extractLastAssistant(messages));
279
+ recordReply(sessionId, reply);
280
+ acknowledgeMentioned(reply);
278
281
  } catch (err) {
279
282
  logError("runStopHandlers:recordReply", err);
280
283
  }