@omercnet/paseo-omp 1.0.1 → 1.0.2-next.180.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",
3
+ "version": "1.0.2-next.180.1",
4
4
  "type": "module",
5
5
  "description": "Paseo integration for OMP, including its direct provider and workspace tooling.",
6
6
  "license": "MIT",
@@ -68,9 +68,9 @@
68
68
  "@types/react": "^19.2.18",
69
69
  "@vitest/coverage-v8": "^5.0.0",
70
70
  "esbuild": "0.28.2",
71
+ "pino": "10.3.1",
71
72
  "react": "19.1.0",
72
73
  "react-native": "0.81.5",
73
- "pino": "10.3.1",
74
74
  "tsx": "^4.20.6",
75
75
  "typescript": "^7.0.2",
76
76
  "vitest": "^5.0.0",
@@ -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"; // x-release-please-version
2
+ export const PASEO_OMP_PACKAGE_VERSION = "1.0.2-next.180.1"; // x-release-please-version
@@ -395,6 +395,11 @@ export class OmpTimelineProjector {
395
395
  private commandText = "";
396
396
  private commandPublishedText = "";
397
397
  private closed = false;
398
+ private lastTodoItems: ReadonlyArray<{
399
+ text: string;
400
+ completed: boolean;
401
+ status: "pending" | "in_progress" | "completed";
402
+ }> | null = null;
398
403
 
399
404
  private readonly dataFilter: OmpPublicDataSerializer;
400
405
  private browserAuthorizationIssuer: ((url: string) => string | undefined) | null = null;
@@ -639,21 +644,17 @@ export class OmpTimelineProjector {
639
644
  if (this.closed) return;
640
645
  if (event.type === "todo_reminder" || event.type === "todo_auto_clear") {
641
646
  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
- });
647
+ const items = todos.slice(0, MAX_TODOS).map((todo, index) => ({
648
+ id: todoPublicId(todo.id, index),
649
+ text: this.dataFilter.text(todo.content, 16_384),
650
+ completed: todo.status === "completed" || todo.status === "abandoned",
651
+ status: (todo.status === "completed" || todo.status === "abandoned"
652
+ ? "completed"
653
+ : todo.status === "blocked"
654
+ ? "pending"
655
+ : todo.status) as "pending" | "in_progress" | "completed",
656
+ }));
657
+ this.publishTodos(items);
657
658
  return;
658
659
  }
659
660
  if (event.type === "goal_updated") {
@@ -1616,10 +1617,37 @@ export class OmpTimelineProjector {
1616
1617
  });
1617
1618
  }
1618
1619
  }
1619
- this.publish({ type: "todo", id: "omp:todos", items });
1620
+ this.publishTodos(items);
1620
1621
  return true;
1621
1622
  }
1622
1623
 
1624
+ private publishTodos(
1625
+ items: Array<{
1626
+ id: string;
1627
+ text: string;
1628
+ completed: boolean;
1629
+ status: "pending" | "in_progress" | "completed";
1630
+ activeForm?: string;
1631
+ }>,
1632
+ ): void {
1633
+ const previous = this.lastTodoItems;
1634
+ if (
1635
+ previous?.length === items.length &&
1636
+ previous.every((previousItem, index) => {
1637
+ const nextItem = items[index];
1638
+ return (
1639
+ nextItem?.text === previousItem.text &&
1640
+ nextItem.completed === previousItem.completed &&
1641
+ nextItem.status === previousItem.status
1642
+ );
1643
+ })
1644
+ ) {
1645
+ return;
1646
+ }
1647
+ this.lastTodoItems = items;
1648
+ this.publish({ type: "todo", id: "omp:todos", items });
1649
+ }
1650
+
1623
1651
  private toolDetail(snapshot: ToolSnapshot): ProviderToolCallDetail {
1624
1652
  const input = jsonRecord(snapshot.input);
1625
1653
  const nestedInput = jsonRecord(input?.input) ?? input;