pi-observational-memory 1.0.3 → 1.0.4

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": "pi-observational-memory",
3
- "version": "1.0.3",
3
+ "version": "1.0.4",
4
4
  "description": "Observational memory extension for pi — cache-friendly tiered compaction with observations and reflections.",
5
5
  "type": "module",
6
6
  "license": "MIT",
package/src/index.ts CHANGED
@@ -8,20 +8,20 @@ import { estimateRawTailTokens, estimateTokens, extractText } from "./tokens.js"
8
8
  import type { MemoryDetails, MemoryState } from "./types.js";
9
9
  import { isMemoryDetails } from "./types.js";
10
10
 
11
- function localDate(epochMs: number): string {
12
- const d = new Date(epochMs);
13
- return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, "0")}-${String(d.getDate()).padStart(2, "0")}`;
11
+ function utcDate(epochMs: number): string {
12
+ if (!Number.isFinite(epochMs)) return "????-??-??";
13
+ return new Date(epochMs).toISOString().slice(0, 10);
14
14
  }
15
15
 
16
- function localTime(epochMs: number): string {
17
- const d = new Date(epochMs);
18
- return `${String(d.getHours()).padStart(2, "0")}:${String(d.getMinutes()).padStart(2, "0")}`;
16
+ function utcTime(epochMs: number): string {
17
+ if (!Number.isFinite(epochMs)) return "??:??";
18
+ return new Date(epochMs).toISOString().slice(11, 16);
19
19
  }
20
20
 
21
21
  function serializeWithTimestamps(messages: Message[]): string {
22
22
  return messages
23
23
  .map((msg): string | null => {
24
- const time = localTime(msg.timestamp);
24
+ const time = utcTime(msg.timestamp);
25
25
  if (msg.role === "user") {
26
26
  const text =
27
27
  typeof msg.content === "string"
@@ -30,7 +30,7 @@ function serializeWithTimestamps(messages: Message[]): string {
30
30
  .filter((b): b is TextContent => b.type === "text")
31
31
  .map((b) => b.text)
32
32
  .join("\n");
33
- return `[User @ ${time}]: ${text}`;
33
+ return `[User @ ${time} UTC]: ${text}`;
34
34
  }
35
35
  if (msg.role === "assistant") {
36
36
  const parts = msg.content.map((b) => {
@@ -41,14 +41,14 @@ function serializeWithTimestamps(messages: Message[]): string {
41
41
  });
42
42
  const body = parts.filter(Boolean).join("\n");
43
43
  if (!body) return null;
44
- return `[Assistant @ ${time}]: ${body}`;
44
+ return `[Assistant @ ${time} UTC]: ${body}`;
45
45
  }
46
46
  // toolResult
47
47
  const text = msg.content
48
48
  .filter((b): b is TextContent => b.type === "text")
49
49
  .map((b) => b.text)
50
50
  .join("\n");
51
- return `[Tool result for ${(msg as ToolResultMessage).toolName} @ ${time}]: ${text}`;
51
+ return `[Tool result for ${(msg as ToolResultMessage).toolName} @ ${time} UTC]: ${text}`;
52
52
  })
53
53
  .filter((line): line is string => line !== null)
54
54
  .join("\n\n");
@@ -131,27 +131,24 @@ export default function observationalMemory(pi: ExtensionAPI) {
131
131
  const allMessages = [...messagesToSummarize, ...turnPrefixMessages];
132
132
  if (allMessages.length === 0) return;
133
133
 
134
- // Bug 1: use local date, not UTC
135
134
  const now = new Date();
136
- const dateStr = localDate(now.getTime());
137
- const timeStr = localTime(now.getTime());
135
+ const dateStr = utcDate(now.getTime());
136
+ const timeStr = utcTime(now.getTime());
138
137
 
139
- // Bug 2: serialize with per-message timestamps
140
138
  const llmMessages = convertToLlm(allMessages);
141
139
  const conversationText = serializeWithTimestamps(llmMessages);
142
140
 
143
- // Bug 3: inform the Observer of the message date range
144
141
  let dateRangeNote = "";
145
142
  if (llmMessages.length > 0) {
146
143
  const timestamps = llmMessages.map((m) => m.timestamp);
147
144
  const firstTs = timestamps.reduce((a, b) => Math.min(a, b));
148
145
  const lastTs = timestamps.reduce((a, b) => Math.max(a, b));
149
- const firstMsgDate = localDate(firstTs);
150
- const lastMsgDate = localDate(lastTs);
146
+ const firstMsgDate = utcDate(firstTs);
147
+ const lastMsgDate = utcDate(lastTs);
151
148
  dateRangeNote =
152
149
  firstMsgDate === lastMsgDate
153
- ? ` Messages in this batch are from ${firstMsgDate}.`
154
- : ` Messages in this batch span ${firstMsgDate} to ${lastMsgDate}.`;
150
+ ? ` Messages in this batch are from ${firstMsgDate} (UTC).`
151
+ : ` Messages in this batch span ${firstMsgDate} to ${lastMsgDate} (UTC).`;
155
152
  }
156
153
 
157
154
  ctx.ui.notify("Observational memory: running observer...", "info");
@@ -171,7 +168,7 @@ export default function observationalMemory(pi: ExtensionAPI) {
171
168
  content: [
172
169
  {
173
170
  type: "text" as const,
174
- text: `Today is ${dateStr}, current time is ${timeStr}.${dateRangeNote}\n\n<current-reflections>\n${state.reflections || "(none yet)"}\n</current-reflections>\n\n<current-observations>\n${state.observations || "(none yet)"}\n</current-observations>\n\nCompress the following conversation into new observations:\n\n<conversation>\n${conversationText}\n</conversation>`,
171
+ text: `Today is ${dateStr}, current time is ${timeStr} UTC.${dateRangeNote}\n\n<current-reflections>\n${state.reflections || "(none yet)"}\n</current-reflections>\n\n<current-observations>\n${state.observations || "(none yet)"}\n</current-observations>\n\nCompress the following conversation into new observations:\n\n<conversation>\n${conversationText}\n</conversation>`,
175
172
  },
176
173
  ],
177
174
  timestamp: Date.now(),
@@ -211,7 +208,7 @@ export default function observationalMemory(pi: ExtensionAPI) {
211
208
  content: [
212
209
  {
213
210
  type: "text" as const,
214
- text: `Today is ${dateStr}.\n\n<current-reflections>\n${state.reflections || "(none yet)"}\n</current-reflections>\n\n<current-observations>\n${state.observations}\n</current-observations>\n\nGarbage-collect these observations. Promote long-lived facts to reflections, prune what's no longer needed, keep what's still active.`,
211
+ text: `Today is ${dateStr} (UTC).\n\n<current-reflections>\n${state.reflections || "(none yet)"}\n</current-reflections>\n\n<current-observations>\n${state.observations}\n</current-observations>\n\nGarbage-collect these observations. Promote long-lived facts to reflections, prune what's no longer needed, keep what's still active.`,
215
212
  },
216
213
  ],
217
214
  timestamp: Date.now(),
package/src/prompts.ts CHANGED
@@ -1,8 +1,8 @@
1
- export const OBSERVER_SYSTEM = `You are an observation agent for a coding assistant. Compress conversation messages into concise, timestamped observations. Messages arrive pre-timestamped as \`[User @ HH:MM]\`, \`[Assistant @ HH:MM]\`, and \`[Tool result for <name> @ HH:MM]\` — use those inline timestamps when assigning times to your observations.
1
+ export const OBSERVER_SYSTEM = `You are an observation agent for a coding assistant. Compress conversation messages into concise, timestamped observations. Messages arrive pre-timestamped as \`[User @ HH:MM UTC]\`, \`[Assistant @ HH:MM UTC]\`, and \`[Tool result for <name> @ HH:MM UTC]\` — use those inline timestamps when assigning times to your observations. All timestamps are UTC.
2
2
 
3
3
  Format as a date-grouped log:
4
4
 
5
- Date: YYYY-MM-DD
5
+ Date: YYYY-MM-DD (UTC)
6
6
  - 🔴 HH:MM Observation text
7
7
  - Sub-observation (no timestamp if same moment)
8
8
  - Sub-observation
@@ -159,7 +159,9 @@ Output EXACTLY two sections with these tags:
159
159
  [Surviving observations in the same date-grouped log format — most should be preserved]
160
160
  </observations>
161
161
 
162
- Do NOT wrap output in code blocks or markdown fences.`;
162
+ Do NOT wrap output in code blocks or markdown fences.
163
+
164
+ All timestamps in observations are UTC.`;
163
165
 
164
166
  export const CONTEXT_USAGE_INSTRUCTIONS = `KNOWLEDGE UPDATES: When observations contain conflicting information, prefer the MOST RECENT observation (check dates). Look for state-change phrases like "will start", "is switching", "changed to", "replacing" as indicators that older information has been superseded.
165
167