@omercnet/paseo-omp 1.0.1-next.165.1 → 1.0.1-next.173.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.
@@ -8,11 +8,11 @@ import { listOmpQuotas } from "../shared/quota";
8
8
  import { ompStoreKey } from "./omp-store-state";
9
9
  import {
10
10
  type QuotaProviderGroup,
11
+ quotaDetailLabel,
11
12
  quotaProviderFromSession,
12
13
  quotaProviderGroups,
13
14
  quotaProviderIconName,
14
15
  quotaProviderLabel,
15
- quotaResetLabel,
16
16
  quotaSeverityFromFraction,
17
17
  } from "./quota-state";
18
18
 
@@ -117,7 +117,7 @@ export function QuotaPopover(props: PluginButtonContentProps) {
117
117
  color={severityColor(group.severity)}
118
118
  />
119
119
  <Text style={styles.groupLabel}>{quotaProviderLabel(group.provider)}</Text>
120
- {current ? <Text style={styles.badge}>CURRENT</Text> : null}
120
+ <Text style={styles.badge}>{current ? "CURRENT" : "RECORDED"}</Text>
121
121
  </View>
122
122
  {group.quotas.map((quota) => {
123
123
  const severity = quotaSeverityFromFraction(quota.usedFraction);
@@ -139,11 +139,7 @@ export function QuotaPopover(props: PluginButtonContentProps) {
139
139
  <View style={styles.track}>
140
140
  <View style={{ height: "100%", width: `${pct}%`, backgroundColor: color }} />
141
141
  </View>
142
- <Text style={styles.detail}>
143
- {[quota.windowLabel, quotaResetLabel(quota.resetsAt)]
144
- .filter(Boolean)
145
- .join(" · ")}
146
- </Text>
142
+ <Text style={styles.detail}>{quotaDetailLabel(quota)}</Text>
147
143
  </View>
148
144
  );
149
145
  })}
@@ -138,3 +138,23 @@ export function quotaResetLabel(resetsAtMs: number | null, nowMs: number = Date.
138
138
  const minutes = Math.floor((remainingMs % 3_600_000) / 60_000);
139
139
  return hours > 0 ? `resets ${hours}h ${minutes}m` : `resets ${minutes}m`;
140
140
  }
141
+
142
+ export function quotaRecordedLabel(recordedAtMs: number, nowMs: number = Date.now()): string {
143
+ const seconds = Math.max(0, Math.floor((nowMs - recordedAtMs) / 1_000));
144
+ if (seconds < 60) return "Recorded just now";
145
+ const minutes = Math.floor(seconds / 60);
146
+ if (minutes < 60) return `Recorded ${minutes}m ago`;
147
+ const hours = Math.floor(minutes / 60);
148
+ if (hours < 24) return `Recorded ${hours}h ago`;
149
+ return `Recorded ${Math.floor(hours / 24)}d ago`;
150
+ }
151
+
152
+ export function quotaDetailLabel(quota: OmpQuota, nowMs: number = Date.now()): string {
153
+ return [
154
+ quota.windowLabel,
155
+ quotaResetLabel(quota.resetsAt, nowMs),
156
+ quotaRecordedLabel(quota.recordedAt, nowMs),
157
+ ]
158
+ .filter(Boolean)
159
+ .join(" · ");
160
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@omercnet/paseo-omp",
3
- "version": "1.0.1-next.165.1",
3
+ "version": "1.0.1-next.173.1",
4
4
  "type": "module",
5
5
  "description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
6
6
  "license": "MIT",
@@ -1,2 +1,2 @@
1
1
  /** Release Please keeps this bundle-safe constant aligned with package.json. */
2
- export const PASEO_OMP_PACKAGE_VERSION = "1.0.1-next.165.1"; // x-release-please-version
2
+ export const PASEO_OMP_PACKAGE_VERSION = "1.0.1-next.173.1"; // x-release-please-version
@@ -395,6 +395,7 @@ export class OmpTimelineProjector {
395
395
  private commandText = "";
396
396
  private commandPublishedText = "";
397
397
  private closed = false;
398
+ private lastTodoItems: ReadonlyArray<{ text: string; completed: boolean }> | null = null;
398
399
 
399
400
  private readonly dataFilter: OmpPublicDataSerializer;
400
401
  private browserAuthorizationIssuer: ((url: string) => string | undefined) | null = null;
@@ -639,21 +640,17 @@ export class OmpTimelineProjector {
639
640
  if (this.closed) return;
640
641
  if (event.type === "todo_reminder" || event.type === "todo_auto_clear") {
641
642
  const todos = event.type === "todo_reminder" ? event.todos : [];
642
- this.publish({
643
- type: "todo",
644
- id: "omp:todos",
645
- items: todos.slice(0, MAX_TODOS).map((todo, index) => ({
646
- id: todoPublicId(todo.id, index),
647
- text: this.dataFilter.text(todo.content, 16_384),
648
- completed: todo.status === "completed" || todo.status === "abandoned",
649
- status:
650
- todo.status === "completed" || todo.status === "abandoned"
651
- ? "completed"
652
- : todo.status === "blocked"
653
- ? "pending"
654
- : todo.status,
655
- })),
656
- });
643
+ const items = todos.slice(0, MAX_TODOS).map((todo, index) => ({
644
+ id: todoPublicId(todo.id, index),
645
+ text: this.dataFilter.text(todo.content, 16_384),
646
+ completed: todo.status === "completed" || todo.status === "abandoned",
647
+ status: (todo.status === "completed" || todo.status === "abandoned"
648
+ ? "completed"
649
+ : todo.status === "blocked"
650
+ ? "pending"
651
+ : todo.status) as "pending" | "in_progress" | "completed",
652
+ }));
653
+ this.publishTodos(items);
657
654
  return;
658
655
  }
659
656
  if (event.type === "goal_updated") {
@@ -1616,10 +1613,35 @@ export class OmpTimelineProjector {
1616
1613
  });
1617
1614
  }
1618
1615
  }
1619
- this.publish({ type: "todo", id: "omp:todos", items });
1616
+ this.publishTodos(items);
1620
1617
  return true;
1621
1618
  }
1622
1619
 
1620
+ private publishTodos(
1621
+ items: Array<{
1622
+ id: string;
1623
+ text: string;
1624
+ completed: boolean;
1625
+ status: "pending" | "in_progress" | "completed";
1626
+ activeForm?: string;
1627
+ }>,
1628
+ ): void {
1629
+ const previous = this.lastTodoItems;
1630
+ if (
1631
+ previous?.length === items.length &&
1632
+ previous.every((previousItem, index) => {
1633
+ const nextItem = items[index];
1634
+ return (
1635
+ nextItem?.text === previousItem.text && nextItem.completed === previousItem.completed
1636
+ );
1637
+ })
1638
+ ) {
1639
+ return;
1640
+ }
1641
+ this.lastTodoItems = items;
1642
+ this.publish({ type: "todo", id: "omp:todos", items });
1643
+ }
1644
+
1623
1645
  private toolDetail(snapshot: ToolSnapshot): ProviderToolCallDetail {
1624
1646
  const input = jsonRecord(snapshot.input);
1625
1647
  const nestedInput = jsonRecord(input?.input) ?? input;