pi-harness-runtime 1.1.12 → 1.1.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/CHANGELOG.md +13 -25
- package/footer-status.ts +49 -4
- package/harness/glm-quota-countdown.test.ts +323 -0
- package/harness/glm-quota-countdown.ts +615 -0
- package/harness/index.ts +12 -0
- package/index.ts +47 -0
- package/package.json +1 -1
- package/packages/a2a-adapter/package.json +1 -1
- package/packages/architecture-generator/package.json +1 -1
- package/packages/auth/package.json +1 -1
- package/packages/autonomous-refactor/package.json +1 -1
- package/packages/autonomous-runtime/package.json +1 -1
- package/packages/cache-strategy/package.json +1 -1
- package/packages/capability-registry/package.json +1 -1
- package/packages/checkpoint/package.json +1 -1
- package/packages/cli-plugin-sdk/package.json +1 -1
- package/packages/clipboard/package.json +1 -1
- package/packages/clipboard-plugin/package.json +1 -1
- package/packages/code-generation/package.json +1 -1
- package/packages/code-review/package.json +1 -1
- package/packages/codex-adapter/package.json +1 -1
- package/packages/config-capture/package.json +1 -1
- package/packages/context-compiler/package.json +1 -1
- package/packages/context-discovery/package.json +1 -1
- package/packages/context-manager/package.json +1 -1
- package/packages/cookie-sanitizer/package.json +1 -1
- package/packages/cost-optimizer/package.json +1 -1
- package/packages/dependency-analyzer/package.json +1 -1
- package/packages/django-plugin/package.json +1 -1
- package/packages/doc-generator/package.json +1 -1
- package/packages/evaluation-engine/package.json +1 -1
- package/packages/evaluation-runner/package.json +1 -1
- package/packages/event-bus/package.json +1 -1
- package/packages/event-store/package.json +1 -1
- package/packages/experience-replay/package.json +1 -1
- package/packages/feedback-collector/package.json +1 -1
- package/packages/file-copy-helper/package.json +1 -1
- package/packages/framework-detector/package.json +1 -1
- package/packages/framework-plugin-sdk/package.json +1 -1
- package/packages/frappe-plugin/package.json +1 -1
- package/packages/generic-web-plugin/package.json +1 -1
- package/packages/health-monitor/package.json +1 -1
- package/packages/intent-analyzer/package.json +1 -1
- package/packages/knowledge-graph/package.json +1 -1
- package/packages/knowledge-retrieval/package.json +1 -1
- package/packages/laravel-plugin/package.json +1 -1
- package/packages/learning-engine/package.json +1 -1
- package/packages/mcp-adapter/package.json +1 -1
- package/packages/memory-engine/package.json +1 -1
- package/packages/milestone-manager/package.json +1 -1
- package/packages/model-registry/package.json +1 -1
- package/packages/nextjs-plugin/package.json +1 -1
- package/packages/notification/package.json +1 -1
- package/packages/observability/package.json +1 -1
- package/packages/okf-indexer/package.json +1 -1
- package/packages/performance-optimizer/package.json +1 -1
- package/packages/privilege-broker/package.json +1 -1
- package/packages/project-analyzer/package.json +1 -1
- package/packages/project-bootstrap/package.json +1 -1
- package/packages/projection-engine/package.json +1 -1
- package/packages/prompt-compiler/package.json +1 -1
- package/packages/prompt-versioning/package.json +1 -1
- package/packages/provider-adapter-sdk/package.json +1 -1
- package/packages/provider-router/package.json +1 -1
- package/packages/provider-selector/package.json +1 -1
- package/packages/providers/package.json +1 -1
- package/packages/quota-manager/dist/tui-usage-monitor.d.ts.map +1 -1
- package/packages/quota-manager/dist/tui-usage-monitor.js +20 -4
- package/packages/quota-manager/dist/tui-usage-monitor.js.map +1 -1
- package/packages/quota-manager/package.json +1 -1
- package/packages/quota-manager/src/tui-usage-monitor.ts +21 -4
- package/packages/rate-limiter/package.json +1 -1
- package/packages/react-vite-plugin/package.json +1 -1
- package/packages/release-manager/package.json +1 -1
- package/packages/requirement-compiler/package.json +1 -1
- package/packages/runtime/package.json +1 -1
- package/packages/scheduler/package.json +1 -1
- package/packages/scheduler-adapter/package.json +1 -1
- package/packages/session/package.json +1 -1
- package/packages/session-api/package.json +1 -1
- package/packages/session-export/package.json +1 -1
- package/packages/shared-context/package.json +1 -1
- package/packages/skill-registry/package.json +1 -1
- package/packages/sprint-planner/package.json +1 -1
- package/packages/subscription-engine/package.json +1 -1
- package/packages/task-compiler/package.json +1 -1
- package/packages/tencentdb-memory/package.json +1 -1
- package/packages/tencentdb-sync/package.json +1 -1
- package/packages/test-data-generator/package.json +1 -1
- package/packages/test-generator/package.json +1 -1
- package/packages/todo-bd-sync/package.json +1 -1
- package/packages/token-estimation/package.json +1 -1
- package/packages/token-optimizer/package.json +1 -1
- package/packages/tui/package.json +1 -1
- package/packages/types/package.json +1 -1
- package/packages/workflow-events/package.json +1 -1
- package/packages/workspace-scanner/package.json +1 -1
- package/packages/worktree/package.json +1 -1
- package/packages/write-review/package.json +1 -1
- package/proactive-compact.ts +71 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,8 +2,20 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
4
|
|
|
5
|
-
### [1.1.
|
|
5
|
+
### [1.1.14](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v1.1.13...v1.1.14) (2026-08-24)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* **glm-quota:** add robust GLM quota countdown with auto-resume ([cb78a2b](https://github.com/ManotLuijiu/pi-harness-runtime/commit/cb78a2bdf5ecf79fc028030d43e3ce40767253cb))
|
|
6
11
|
|
|
12
|
+
### [1.1.13](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v1.1.12...v1.1.13) (2026-08-21)
|
|
13
|
+
|
|
14
|
+
### Bug Fixes
|
|
15
|
+
|
|
16
|
+
* **runtime:** auto-resume after provider overload ([7bd98bb](https://github.com/ManotLuijiu/pi-harness-runtime/commit/7bd98bb885536667a5eb35cfe6de45a06dc77269))
|
|
17
|
+
|
|
18
|
+
### [1.1.12](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v1.1.11...v1.1.12) (2026-08-20)
|
|
7
19
|
|
|
8
20
|
### Bug Fixes
|
|
9
21
|
|
|
@@ -11,7 +23,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
11
23
|
|
|
12
24
|
### [1.1.11](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.42...v1.1.11) (2026-08-20)
|
|
13
25
|
|
|
14
|
-
|
|
15
26
|
### Features
|
|
16
27
|
|
|
17
28
|
* add quota scrapers for ChatGPT and GLM providers ([17f9d0e](https://github.com/ManotLuijiu/pi-harness-runtime/commit/17f9d0e7d6f486210a30244f2fecc0131e8b7d57))
|
|
@@ -19,7 +30,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
19
30
|
* index.ts ([d0fc002](https://github.com/ManotLuijiu/pi-harness-runtime/commit/d0fc002d6d16e59318155677cb6128846bc993a4))
|
|
20
31
|
* update workflow run name to show version number ([ee8c59f](https://github.com/ManotLuijiu/pi-harness-runtime/commit/ee8c59fec4c0116a5948b2ae04ad4609edaed3ac))
|
|
21
32
|
|
|
22
|
-
|
|
23
33
|
### Bug Fixes
|
|
24
34
|
|
|
25
35
|
* add typescript as devDependency, build notification dist ([c76b522](https://github.com/ManotLuijiu/pi-harness-runtime/commit/c76b5221c33262c38615c6117ecf9db13ab7b5d2))
|
|
@@ -42,7 +52,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
42
52
|
|
|
43
53
|
### [1.1.10](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.42...v1.1.10) (2026-08-20)
|
|
44
54
|
|
|
45
|
-
|
|
46
55
|
### Features
|
|
47
56
|
|
|
48
57
|
* add quota scrapers for ChatGPT and GLM providers ([17f9d0e](https://github.com/ManotLuijiu/pi-harness-runtime/commit/17f9d0e7d6f486210a30244f2fecc0131e8b7d57))
|
|
@@ -50,7 +59,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
50
59
|
* index.ts ([d0fc002](https://github.com/ManotLuijiu/pi-harness-runtime/commit/d0fc002d6d16e59318155677cb6128846bc993a4))
|
|
51
60
|
* update workflow run name to show version number ([ee8c59f](https://github.com/ManotLuijiu/pi-harness-runtime/commit/ee8c59fec4c0116a5948b2ae04ad4609edaed3ac))
|
|
52
61
|
|
|
53
|
-
|
|
54
62
|
### Bug Fixes
|
|
55
63
|
|
|
56
64
|
* add typescript as devDependency, build notification dist ([c76b522](https://github.com/ManotLuijiu/pi-harness-runtime/commit/c76b5221c33262c38615c6117ecf9db13ab7b5d2))
|
|
@@ -72,7 +80,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
72
80
|
|
|
73
81
|
### [0.10.30](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.29...v0.10.30) (2026-08-11)
|
|
74
82
|
|
|
75
|
-
|
|
76
83
|
### Bug Fixes
|
|
77
84
|
|
|
78
85
|
* **file-copy-helper:** require explicit intent for copy rule injection ([ddfece0](https://github.com/ManotLuijiu/pi-harness-runtime/commit/ddfece0bcbf440dfd4edc0d442b078e84e906c68))
|
|
@@ -91,13 +98,11 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
91
98
|
|
|
92
99
|
### [0.10.27](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.14...v0.10.27) (2026-08-10)
|
|
93
100
|
|
|
94
|
-
|
|
95
101
|
### Features
|
|
96
102
|
|
|
97
103
|
* create packages for missing RFC implementations ([e6ce2ff](https://github.com/ManotLuijiu/pi-harness-runtime/commit/e6ce2ffdd455b33c55212ec2b61eedcdcfb43b5e))
|
|
98
104
|
* **file-copy-helper:** inject cp rule when user asks to mimic/copy files ([f089f0d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/f089f0d5e3e933231c99ee38cf18fa208c8eb8b2))
|
|
99
105
|
|
|
100
|
-
|
|
101
106
|
### Bug Fixes
|
|
102
107
|
|
|
103
108
|
* align TS formatting with main (remove 3-line logError) ([2515ad5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/2515ad54f43f0566123c686d3ec4f92ad363ea13))
|
|
@@ -113,12 +118,10 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
113
118
|
|
|
114
119
|
### [0.10.26](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.14...v0.10.26) (2026-08-10)
|
|
115
120
|
|
|
116
|
-
|
|
117
121
|
### Features
|
|
118
122
|
|
|
119
123
|
* **file-copy-helper:** inject cp rule when user asks to mimic/copy files ([f089f0d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/f089f0d5e3e933231c99ee38cf18fa208c8eb8b2))
|
|
120
124
|
|
|
121
|
-
|
|
122
125
|
### Bug Fixes
|
|
123
126
|
|
|
124
127
|
* align TS formatting with main (remove 3-line logError) ([2515ad5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/2515ad54f43f0566123c686d3ec4f92ad363ea13))
|
|
@@ -134,12 +137,10 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
134
137
|
|
|
135
138
|
### [0.10.25](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.14...v0.10.25) (2026-08-10)
|
|
136
139
|
|
|
137
|
-
|
|
138
140
|
### Features
|
|
139
141
|
|
|
140
142
|
* **file-copy-helper:** inject cp rule when user asks to mimic/copy files ([f089f0d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/f089f0d5e3e933231c99ee38cf18fa208c8eb8b2))
|
|
141
143
|
|
|
142
|
-
|
|
143
144
|
### Bug Fixes
|
|
144
145
|
|
|
145
146
|
* align TS formatting with main (remove 3-line logError) ([2515ad5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/2515ad54f43f0566123c686d3ec4f92ad363ea13))
|
|
@@ -154,12 +155,10 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
154
155
|
|
|
155
156
|
### [0.10.24](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.14...v0.10.24) (2026-08-09)
|
|
156
157
|
|
|
157
|
-
|
|
158
158
|
### Features
|
|
159
159
|
|
|
160
160
|
* **file-copy-helper:** inject cp rule when user asks to mimic/copy files ([f089f0d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/f089f0d5e3e933231c99ee38cf18fa208c8eb8b2))
|
|
161
161
|
|
|
162
|
-
|
|
163
162
|
### Bug Fixes
|
|
164
163
|
|
|
165
164
|
* align TS formatting with main (remove 3-line logError) ([2515ad5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/2515ad54f43f0566123c686d3ec4f92ad363ea13))
|
|
@@ -173,12 +172,10 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
173
172
|
|
|
174
173
|
### [0.10.23](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.14...v0.10.23) (2026-08-09)
|
|
175
174
|
|
|
176
|
-
|
|
177
175
|
### Features
|
|
178
176
|
|
|
179
177
|
* **file-copy-helper:** inject cp rule when user asks to mimic/copy files ([f089f0d](https://github.com/ManotLuijiu/pi-harness-runtime/commit/f089f0d5e3e933231c99ee38cf18fa208c8eb8b2))
|
|
180
178
|
|
|
181
|
-
|
|
182
179
|
### Bug Fixes
|
|
183
180
|
|
|
184
181
|
* align TS formatting with main (remove 3-line logError) ([2515ad5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/2515ad54f43f0566123c686d3ec4f92ad363ea13))
|
|
@@ -191,7 +188,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
191
188
|
|
|
192
189
|
### [0.10.22](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.14...v0.10.22) (2026-08-09)
|
|
193
190
|
|
|
194
|
-
|
|
195
191
|
### Bug Fixes
|
|
196
192
|
|
|
197
193
|
* align TS formatting with main (remove 3-line logError) ([2515ad5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/2515ad54f43f0566123c686d3ec4f92ad363ea13))
|
|
@@ -204,7 +200,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
204
200
|
|
|
205
201
|
### [0.10.21](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.14...v0.10.21) (2026-08-08)
|
|
206
202
|
|
|
207
|
-
|
|
208
203
|
### Bug Fixes
|
|
209
204
|
|
|
210
205
|
* align TS formatting with main (remove 3-line logError) ([2515ad5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/2515ad54f43f0566123c686d3ec4f92ad363ea13))
|
|
@@ -216,7 +211,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
216
211
|
|
|
217
212
|
### [0.10.20](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.14...v0.10.20) (2026-08-08)
|
|
218
213
|
|
|
219
|
-
|
|
220
214
|
### Bug Fixes
|
|
221
215
|
|
|
222
216
|
* align TS formatting with main (remove 3-line logError) ([2515ad5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/2515ad54f43f0566123c686d3ec4f92ad363ea13))
|
|
@@ -227,14 +221,12 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
227
221
|
|
|
228
222
|
### [0.10.11](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.10.9...v0.10.11) (2026-08-03)
|
|
229
223
|
|
|
230
|
-
|
|
231
224
|
### Features
|
|
232
225
|
|
|
233
226
|
* add knowledge-retrieval package for TencentDB-Agent-Memory MCP (RFC-0105) ([a4558d0](https://github.com/ManotLuijiu/pi-harness-runtime/commit/a4558d098c7b662b01d9502c8560219eafbf1941))
|
|
234
227
|
* add okf-indexer package for SKILL.md indexing (RFC-0106) ([a3cc7da](https://github.com/ManotLuijiu/pi-harness-runtime/commit/a3cc7da0a390d5c8176d0d871418780facabcb5c))
|
|
235
228
|
* extend a2a-adapter with discovery server (RFC-0104) ([0c4fc18](https://github.com/ManotLuijiu/pi-harness-runtime/commit/0c4fc1826fd55f10305bfbd894b32b5b55f1ab86))
|
|
236
229
|
|
|
237
|
-
|
|
238
230
|
### Bug Fixes
|
|
239
231
|
|
|
240
232
|
* act on CONFLICTING immediately even when mergeStateStatus=DIRTY ([b7f290e](https://github.com/ManotLuijiu/pi-harness-runtime/commit/b7f290ece8e0e989635aacc456fffd4ee19183ec))
|
|
@@ -256,14 +248,12 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
256
248
|
|
|
257
249
|
## [0.10.0](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.24...v0.10.0) (2026-07-30)
|
|
258
250
|
|
|
259
|
-
|
|
260
251
|
### Features
|
|
261
252
|
|
|
262
253
|
* blackboard-coordinated loop controller with early exit ([f3cf3ea](https://github.com/ManotLuijiu/pi-harness-runtime/commit/f3cf3ea479170dd75b7e9a7c3515dde230bb0da7))
|
|
263
254
|
* herdr inter-agent event bus for autonomous code review ([1490cdd](https://github.com/ManotLuijiu/pi-harness-runtime/commit/1490cddec80dac812065c3c681984524ad3194e1))
|
|
264
255
|
* loop controller for write-review cycles with early exit ([95066ce](https://github.com/ManotLuijiu/pi-harness-runtime/commit/95066ce8cb58c55eaa14d69fd826b5aaff680c16))
|
|
265
256
|
|
|
266
|
-
|
|
267
257
|
### Bug Fixes
|
|
268
258
|
|
|
269
259
|
* **clipboard:** pass UTF-8 bytes directly to xclip via stdin, avoid pipeline corruption ([051ca18](https://github.com/ManotLuijiu/pi-harness-runtime/commit/051ca18fb1deb1169a8a4da16aa9314d2e6ec66d))
|
|
@@ -276,7 +266,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
276
266
|
|
|
277
267
|
### [0.9.25](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.21...v0.9.25) (2026-07-27)
|
|
278
268
|
|
|
279
|
-
|
|
280
269
|
### Bug Fixes
|
|
281
270
|
|
|
282
271
|
* **ci:** explicitly list test files to exclude cookie auth tests in release workflow ([1c96002](https://github.com/ManotLuijiu/pi-harness-runtime/commit/1c9600232282a1ef9323300fbd763c962aff49f6))
|
|
@@ -287,7 +276,6 @@ All notable changes to this project will be documented in this file. See [standa
|
|
|
287
276
|
|
|
288
277
|
### [0.9.24](https://github.com/ManotLuijiu/pi-harness-runtime/compare/v0.9.21...v0.9.24) (2026-07-27)
|
|
289
278
|
|
|
290
|
-
|
|
291
279
|
### Bug Fixes
|
|
292
280
|
|
|
293
281
|
* **ci:** skip browser auth tests in release workflow ([6117ed5](https://github.com/ManotLuijiu/pi-harness-runtime/commit/6117ed5af11357c6f12d11f4cd8d3441de519043))
|
package/footer-status.ts
CHANGED
|
@@ -6,6 +6,51 @@ import {
|
|
|
6
6
|
type ParsedContextWindowUsage,
|
|
7
7
|
type ParsedQuotaUsageData,
|
|
8
8
|
} from "./status-parsers.ts";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Format countdown seconds into a human-readable string for TUI display.
|
|
12
|
+
* Examples: "4h 32m", "45m 12s", "12s"
|
|
13
|
+
*/
|
|
14
|
+
export function formatCountdownForDisplay(seconds: number): string {
|
|
15
|
+
if (seconds <= 0) return "RESET";
|
|
16
|
+
|
|
17
|
+
const days = Math.floor(seconds / 86400);
|
|
18
|
+
const hours = Math.floor((seconds % 86400) / 3600);
|
|
19
|
+
const minutes = Math.floor((seconds % 3600) / 60);
|
|
20
|
+
const secs = seconds % 60;
|
|
21
|
+
|
|
22
|
+
if (days > 0) {
|
|
23
|
+
return `${days}d ${hours}h`;
|
|
24
|
+
}
|
|
25
|
+
if (hours > 0) {
|
|
26
|
+
return `${hours}h ${minutes}m`;
|
|
27
|
+
}
|
|
28
|
+
if (minutes > 0) {
|
|
29
|
+
return `${minutes}m ${secs}s`;
|
|
30
|
+
}
|
|
31
|
+
return `${secs}s`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Build a TUI-signal exhaustion status line with optional countdown.
|
|
36
|
+
* This is used when a provider (like GLM) hits its limit.
|
|
37
|
+
*/
|
|
38
|
+
export function buildExhaustionStatusLine(
|
|
39
|
+
provider: string,
|
|
40
|
+
view: ProviderMirrorRecord,
|
|
41
|
+
countdownSeconds?: number,
|
|
42
|
+
): string {
|
|
43
|
+
const label = providerDisplayName(provider);
|
|
44
|
+
const limitType = view.limitType ?? "tokens";
|
|
45
|
+
const reset = view.resets_at ?? view.h5_resets_at ?? "soon";
|
|
46
|
+
|
|
47
|
+
if (countdownSeconds !== undefined && countdownSeconds > 0) {
|
|
48
|
+
const countdown = formatCountdownForDisplay(countdownSeconds);
|
|
49
|
+
return `${label}: ⏳ ${countdown} (${limitType} hit)`;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return `${label}: limit hit (${limitType}), reset ${reset}`;
|
|
53
|
+
}
|
|
9
54
|
import { getProviderLabel } from "./packages/types/src/ai-providers.js";
|
|
10
55
|
import {
|
|
11
56
|
providerHasContinuousScrape,
|
|
@@ -153,14 +198,14 @@ export function buildFooterStatusValue(
|
|
|
153
198
|
const weeklyResets = view.weekly_resets_at ?? "soon";
|
|
154
199
|
|
|
155
200
|
let statusLine: string;
|
|
156
|
-
if (view.h5_used_pct
|
|
201
|
+
if (view.h5_used_pct === undefined) {
|
|
202
|
+
// OpenAI: weekly-only (no 5h window)
|
|
203
|
+
statusLine = `week: ${weeklyLeft.toFixed(0)}% left (resets ${weeklyResets})`;
|
|
204
|
+
} else {
|
|
157
205
|
// MiniMax: has both 5h and weekly windows
|
|
158
206
|
const h5Pct = view.h5_used_pct;
|
|
159
207
|
const h5Left = Math.max(0, 100 - h5Pct);
|
|
160
208
|
statusLine = `5h: ${h5Left.toFixed(0)}% left · week: ${weeklyLeft.toFixed(0)}% left`;
|
|
161
|
-
} else {
|
|
162
|
-
// OpenAI: weekly-only (no 5h window)
|
|
163
|
-
statusLine = `week: ${weeklyLeft.toFixed(0)}% left (resets ${weeklyResets})`;
|
|
164
209
|
}
|
|
165
210
|
|
|
166
211
|
const freshnessSuffix =
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GLM Quota Countdown Tests
|
|
3
|
+
*
|
|
4
|
+
* Run with: bun test harness/glm-quota-countdown.test.ts
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
8
|
+
import {
|
|
9
|
+
parseGLMResetTime,
|
|
10
|
+
formatCountdown,
|
|
11
|
+
GLMQuotaCountdown,
|
|
12
|
+
} from "./glm-quota-countdown.js";
|
|
13
|
+
|
|
14
|
+
describe("parseGLMResetTime", () => {
|
|
15
|
+
it("parses full datetime format 'reset at 2026-08-25 01:47:16'", () => {
|
|
16
|
+
const result = parseGLMResetTime(
|
|
17
|
+
'Error: 429: {"code":"1308","message":"Usage limit reached for 5 hour. Your limit will reset at 2026-08-25 01:47:16"}',
|
|
18
|
+
);
|
|
19
|
+
expect(result).not.toBeNull();
|
|
20
|
+
const date = new Date(result!);
|
|
21
|
+
expect(date.getUTCFullYear()).toBe(2026);
|
|
22
|
+
expect(date.getUTCMonth()).toBe(7); // August is 7 (0-indexed)
|
|
23
|
+
expect(date.getUTCDate()).toBe(25);
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("parses full datetime format with T separator", () => {
|
|
27
|
+
const result = parseGLMResetTime(
|
|
28
|
+
"Your limit will reset at 2026-08-25T01:47:16",
|
|
29
|
+
);
|
|
30
|
+
expect(result).not.toBeNull();
|
|
31
|
+
const date = new Date(result!);
|
|
32
|
+
expect(date.getUTCFullYear()).toBe(2026);
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
it("parses time-only format when date is implied", () => {
|
|
36
|
+
const result = parseGLMResetTime("will reset at 14:30:00");
|
|
37
|
+
expect(result).not.toBeNull();
|
|
38
|
+
const date = new Date(result!);
|
|
39
|
+
expect(date.getUTCHours()).toBe(14);
|
|
40
|
+
expect(date.getUTCMinutes()).toBe(30);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("returns null for non-GLM messages", () => {
|
|
44
|
+
expect(parseGLMResetTime("Hello world")).toBeNull();
|
|
45
|
+
expect(parseGLMResetTime("OpenAI rate limit exceeded")).toBeNull();
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
it("handles GLM error with code 1308", () => {
|
|
49
|
+
const result = parseGLMResetTime(
|
|
50
|
+
'{"code":"1308","message":"Usage limit reached for 5 hour. Your limit will reset at 2026-08-25 01:47:16"}',
|
|
51
|
+
);
|
|
52
|
+
expect(result).not.toBeNull();
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
it("parses alternative format 'reset at 2026-08-25 01:47:16'", () => {
|
|
56
|
+
const result = parseGLMResetTime(
|
|
57
|
+
"reset at 2026-08-25 01:47:16 - please retry later",
|
|
58
|
+
);
|
|
59
|
+
expect(result).not.toBeNull();
|
|
60
|
+
});
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
describe("formatCountdown", () => {
|
|
64
|
+
it("formats days, hours, minutes, seconds", () => {
|
|
65
|
+
expect(formatCountdown(90061)).toBe("1d 1h 1m 1s"); // 1 day, 1 hour, 1 min, 1 sec
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it("formats hours and minutes when no days", () => {
|
|
69
|
+
expect(formatCountdown(3661)).toBe("1h 1m 1s");
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("formats minutes and seconds when no hours", () => {
|
|
73
|
+
expect(formatCountdown(125)).toBe("2m 5s");
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
it("formats seconds only when less than a minute", () => {
|
|
77
|
+
expect(formatCountdown(45)).toBe("45s");
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("returns 'RESET NOW' for zero or negative", () => {
|
|
81
|
+
expect(formatCountdown(0)).toBe("RESET NOW");
|
|
82
|
+
expect(formatCountdown(-100)).toBe("RESET NOW");
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("handles exactly 24 hours", () => {
|
|
86
|
+
expect(formatCountdown(86400)).toBe("1d 0h 0m 0s");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("handles exactly 1 hour", () => {
|
|
90
|
+
expect(formatCountdown(3600)).toBe("1h 0m 0s");
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
it("handles exactly 1 minute", () => {
|
|
94
|
+
expect(formatCountdown(60)).toBe("1m 0s");
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
describe("GLMQuotaCountdown", () => {
|
|
99
|
+
let countdown: GLMQuotaCountdown;
|
|
100
|
+
let mockCheckpoint: any;
|
|
101
|
+
let mockTransition: any;
|
|
102
|
+
let mockReadProvider: any;
|
|
103
|
+
|
|
104
|
+
beforeEach(() => {
|
|
105
|
+
countdown = new GLMQuotaCountdown();
|
|
106
|
+
|
|
107
|
+
// Track calls for verification
|
|
108
|
+
mockCheckpoint = { jobId: "test-job", status: "paused_quota" };
|
|
109
|
+
mockTransition = { success: true };
|
|
110
|
+
mockReadProvider = null;
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
afterEach(() => {
|
|
114
|
+
countdown.dispose();
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
describe("hasCountdown / getCountdown", () => {
|
|
118
|
+
it("returns false initially for unknown job", () => {
|
|
119
|
+
expect(countdown.hasCountdown("unknown")).toBe(false);
|
|
120
|
+
expect(countdown.getCountdown("unknown")).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
describe("startCountdown", () => {
|
|
125
|
+
it("starts countdown and stores state", async () => {
|
|
126
|
+
const futureDate = new Date(Date.now() + 3600000).toISOString(); // 1 hour from now
|
|
127
|
+
const mockMachine = createMockMachine(mockCheckpoint, mockTransition);
|
|
128
|
+
const mockMirror = createMockMirror(() => mockReadProvider, noopWrite);
|
|
129
|
+
|
|
130
|
+
const state = await countdown.startCountdown(
|
|
131
|
+
"job-1",
|
|
132
|
+
futureDate,
|
|
133
|
+
mockMirror,
|
|
134
|
+
mockMachine,
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
expect(state.jobId).toBe("job-1");
|
|
138
|
+
expect(state.resetAt).toBe(futureDate);
|
|
139
|
+
expect(state.totalSeconds).toBeGreaterThan(3500); // ~1 hour
|
|
140
|
+
expect(countdown.hasCountdown("job-1")).toBe(true);
|
|
141
|
+
});
|
|
142
|
+
|
|
143
|
+
it("cancels existing countdown before starting new one", async () => {
|
|
144
|
+
const futureDate1 = new Date(Date.now() + 3600000).toISOString();
|
|
145
|
+
const futureDate2 = new Date(Date.now() + 7200000).toISOString();
|
|
146
|
+
const mockMachine = createMockMachine(mockCheckpoint, mockTransition);
|
|
147
|
+
const mockMirror = createMockMirror(() => mockReadProvider, noopWrite);
|
|
148
|
+
|
|
149
|
+
await countdown.startCountdown(
|
|
150
|
+
"job-1",
|
|
151
|
+
futureDate1,
|
|
152
|
+
mockMirror,
|
|
153
|
+
mockMachine,
|
|
154
|
+
);
|
|
155
|
+
|
|
156
|
+
const state = await countdown.startCountdown(
|
|
157
|
+
"job-1",
|
|
158
|
+
futureDate2,
|
|
159
|
+
mockMirror,
|
|
160
|
+
mockMachine,
|
|
161
|
+
);
|
|
162
|
+
|
|
163
|
+
// Should have only one countdown
|
|
164
|
+
expect(countdown.getAllCountdowns().length).toBe(1);
|
|
165
|
+
expect(state.resetAt).toBe(futureDate2);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it("updates mirror store with reset time", async () => {
|
|
169
|
+
const futureDate = new Date(Date.now() + 3600000).toISOString();
|
|
170
|
+
const mockMachine = createMockMachine(mockCheckpoint, mockTransition);
|
|
171
|
+
let capturedRecord: any = null;
|
|
172
|
+
const mockMirror = createMockMirror(
|
|
173
|
+
() => mockReadProvider,
|
|
174
|
+
(_p, r) => {
|
|
175
|
+
capturedRecord = r;
|
|
176
|
+
},
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
await countdown.startCountdown("job-1", futureDate, mockMirror, mockMachine);
|
|
180
|
+
|
|
181
|
+
expect(capturedRecord).not.toBeNull();
|
|
182
|
+
expect(capturedRecord.provider).toBe("glm");
|
|
183
|
+
expect(capturedRecord.exhausted).toBe(true);
|
|
184
|
+
expect(capturedRecord.h5_resets_at).toBe(futureDate);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
describe("startFromError", () => {
|
|
189
|
+
it("parses reset time from error message", async () => {
|
|
190
|
+
const errorMsg =
|
|
191
|
+
'Error: 429: {"code":"1308","message":"Usage limit reached for 5 hour. Your limit will reset at 2099-12-31 23:59:59"}';
|
|
192
|
+
const mockMachine = createMockMachine(mockCheckpoint, mockTransition);
|
|
193
|
+
const mockMirror = createMockMirror(() => mockReadProvider, noopWrite);
|
|
194
|
+
|
|
195
|
+
const state = await countdown.startFromError(
|
|
196
|
+
"job-1",
|
|
197
|
+
errorMsg,
|
|
198
|
+
mockMirror,
|
|
199
|
+
mockMachine,
|
|
200
|
+
);
|
|
201
|
+
|
|
202
|
+
expect(state).not.toBeNull();
|
|
203
|
+
expect(state!.jobId).toBe("job-1");
|
|
204
|
+
expect(countdown.hasCountdown("job-1")).toBe(true);
|
|
205
|
+
});
|
|
206
|
+
|
|
207
|
+
it("returns null when reset time cannot be parsed", async () => {
|
|
208
|
+
const mockMachine = createMockMachine(mockCheckpoint, mockTransition);
|
|
209
|
+
const mockMirror = createMockMirror(() => mockReadProvider, noopWrite);
|
|
210
|
+
|
|
211
|
+
const state = await countdown.startFromError(
|
|
212
|
+
"job-1",
|
|
213
|
+
"Some unrelated error message",
|
|
214
|
+
mockMirror,
|
|
215
|
+
mockMachine,
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
expect(state).toBeNull();
|
|
219
|
+
});
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
describe("cancelCountdown", () => {
|
|
223
|
+
it("cancels active countdown", async () => {
|
|
224
|
+
const futureDate = new Date(Date.now() + 3600000).toISOString();
|
|
225
|
+
const mockMachine = createMockMachine(mockCheckpoint, mockTransition);
|
|
226
|
+
const mockMirror = createMockMirror(() => mockReadProvider, noopWrite);
|
|
227
|
+
|
|
228
|
+
await countdown.startCountdown("job-1", futureDate, mockMirror, mockMachine);
|
|
229
|
+
|
|
230
|
+
expect(countdown.hasCountdown("job-1")).toBe(true);
|
|
231
|
+
|
|
232
|
+
countdown.cancelCountdown("job-1");
|
|
233
|
+
|
|
234
|
+
expect(countdown.hasCountdown("job-1")).toBe(false);
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("handles cancelling non-existent countdown", () => {
|
|
238
|
+
// Should not throw
|
|
239
|
+
expect(() => countdown.cancelCountdown("non-existent")).not.toThrow();
|
|
240
|
+
});
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
describe("onTick callback", () => {
|
|
244
|
+
it("receives tick events", async () => {
|
|
245
|
+
const ticks: any[] = [];
|
|
246
|
+
const futureDate = new Date(Date.now() + 3600000).toISOString();
|
|
247
|
+
const mockMachine = createMockMachine(mockCheckpoint, mockTransition);
|
|
248
|
+
const mockMirror = createMockMirror(() => mockReadProvider, noopWrite);
|
|
249
|
+
|
|
250
|
+
const unsubscribe = countdown.onTick((event) => {
|
|
251
|
+
ticks.push(event);
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
await countdown.startCountdown("job-1", futureDate, mockMirror, mockMachine);
|
|
255
|
+
|
|
256
|
+
// Wait a bit for initial tick
|
|
257
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
258
|
+
|
|
259
|
+
expect(ticks.length).toBeGreaterThan(0);
|
|
260
|
+
expect(ticks[0].jobId).toBe("job-1");
|
|
261
|
+
expect(ticks[0].remainingFormatted).toBeDefined();
|
|
262
|
+
|
|
263
|
+
unsubscribe();
|
|
264
|
+
});
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
describe("getAllCountdowns", () => {
|
|
268
|
+
it("returns all active countdowns", async () => {
|
|
269
|
+
const futureDate = new Date(Date.now() + 3600000).toISOString();
|
|
270
|
+
const mockMachine = createMockMachine(mockCheckpoint, mockTransition);
|
|
271
|
+
const mockMirror = createMockMirror(() => mockReadProvider, noopWrite);
|
|
272
|
+
|
|
273
|
+
await countdown.startCountdown("job-1", futureDate, mockMirror, mockMachine);
|
|
274
|
+
|
|
275
|
+
await countdown.startCountdown("job-2", futureDate, mockMirror, mockMachine);
|
|
276
|
+
|
|
277
|
+
const all = countdown.getAllCountdowns();
|
|
278
|
+
expect(all.length).toBe(2);
|
|
279
|
+
expect(all.map((c) => c.jobId)).toContain("job-1");
|
|
280
|
+
expect(all.map((c) => c.jobId)).toContain("job-2");
|
|
281
|
+
});
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
describe("dispose", () => {
|
|
285
|
+
it("clears all timers and countdowns", async () => {
|
|
286
|
+
const futureDate = new Date(Date.now() + 3600000).toISOString();
|
|
287
|
+
const mockMachine = createMockMachine(mockCheckpoint, mockTransition);
|
|
288
|
+
const mockMirror = createMockMirror(() => mockReadProvider, noopWrite);
|
|
289
|
+
|
|
290
|
+
await countdown.startCountdown("job-1", futureDate, mockMirror, mockMachine);
|
|
291
|
+
|
|
292
|
+
await countdown.startCountdown("job-2", futureDate, mockMirror, mockMachine);
|
|
293
|
+
|
|
294
|
+
expect(countdown.getAllCountdowns().length).toBe(2);
|
|
295
|
+
|
|
296
|
+
countdown.dispose();
|
|
297
|
+
|
|
298
|
+
expect(countdown.getAllCountdowns().length).toBe(0);
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
// No-op write function for tests that don't need to verify write calls
|
|
304
|
+
function noopWrite(_provider: string, _record: any): void {}
|
|
305
|
+
|
|
306
|
+
// Helper: Create mock JobStateMachine
|
|
307
|
+
function createMockMachine(getCheckpointResult: any, transitionResult: any) {
|
|
308
|
+
return {
|
|
309
|
+
getCheckpoint: () => getCheckpointResult,
|
|
310
|
+
transition: async () => transitionResult,
|
|
311
|
+
} as any;
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Helper: Create mock MirrorStore
|
|
315
|
+
function createMockMirror(
|
|
316
|
+
readProviderFn: () => any,
|
|
317
|
+
writeProviderFn: (provider: string, record: any) => void,
|
|
318
|
+
) {
|
|
319
|
+
return {
|
|
320
|
+
readProvider: readProviderFn,
|
|
321
|
+
writeProvider: writeProviderFn,
|
|
322
|
+
} as any;
|
|
323
|
+
}
|