vibeostheog 0.22.12 → 0.22.16
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 +39 -105
- package/package.json +5 -5
- package/src/lib/api-client.js +6 -0
- package/src/lib/hooks/footer.js +19 -14
- package/src/lib/hooks/tool-execute.js +7 -2
- package/src/lib/state.js +61 -16
- package/src/vibeOS-lib/blackbox/local-stub.js +32 -8
- package/src/vibeOS-lib/blackbox/meta-controller.js +2 -1
- package/src/vibeOS-lib/blackbox/resolution-tracker.js +35 -8
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,28 @@
|
|
|
1
|
+
## 0.22.16
|
|
2
|
+
|
|
3
|
+
- test: add 12 cache isolation scenarios (no cross-session/project hallucination)
|
|
4
|
+
- test: add 3 regression tests for setApiToken fallback-mode reset
|
|
5
|
+
- chore: remove codex branding
|
|
6
|
+
|
|
7
|
+
## 0.22.15
|
|
8
|
+
|
|
9
|
+
- fix: remove user-wide cache fallback from getScratchpadHit()
|
|
10
|
+
no more SCRATCHPAD_GLOBAL_DIR — cache scope is session/project only
|
|
11
|
+
- fix: prefer session cache over global cache in getScratchpadHit()
|
|
12
|
+
swap lookup order on direct-hash and pointer-resolved paths
|
|
13
|
+
- fix: setApiToken() now resets _apiFallbackMode so a token update
|
|
14
|
+
breaks out of permanent API-fallback deadlock
|
|
15
|
+
- fix: syncApiTokenFromDisk() else branch also clears fallback state
|
|
16
|
+
- test: add 12 cache isolation scenarios (no cross-session hallucination)
|
|
17
|
+
- test: 3 regression tests for setApiToken fallback-mode reset
|
|
18
|
+
- test: add cache isolation test suite (15 total regression tests)
|
|
19
|
+
|
|
20
|
+
## 0.22.14
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
## 0.22.13
|
|
24
|
+
|
|
25
|
+
|
|
1
26
|
## 0.22.12
|
|
2
27
|
- fix: harden scratchpad cache
|
|
3
28
|
|
|
@@ -19,7 +44,6 @@
|
|
|
19
44
|
## 0.22.6
|
|
20
45
|
- feat: wire CostAnomalyDetector into tool-execute hook
|
|
21
46
|
- feat: replace TokenAnomalyDetector with CostAnomalyDetector
|
|
22
|
-
- feat: replace TokenAnomalyDetector with CostAnomalyDetector
|
|
23
47
|
- fix: bin/setup.js now delegates to deploy.mjs for proper plugin install
|
|
24
48
|
- fix: restore anomaly detector class in TS source, add mega regression tests
|
|
25
49
|
- fix: read path prefers global scratchpad over stale session-local copies
|
|
@@ -51,7 +75,6 @@ Merge pull request #91 from DrunkkToys/pr/cache-write-savings
|
|
|
51
75
|
## 0.20.15
|
|
52
76
|
- feat: dashboard blackbox telemetry — bidirectional BE/FE sync
|
|
53
77
|
- fix: mock auth and clear OPENCODE_MODEL in bootstrap test, commit blackbox .js for CI
|
|
54
|
-
- fix: mock auth and clear OPENCODE_MODEL in bootstrap test, commit blackbox .js for CI
|
|
55
78
|
- docs: fix speed mode quality rating in comparison table (#83)
|
|
56
79
|
- docs: fix token defaults in env vars table
|
|
57
80
|
- docs: update README to reflect actual features and fix inaccuracies
|
|
@@ -93,10 +116,9 @@ release: v0.20.13 — holistic CLI footer fix + regression tests (#80)
|
|
|
93
116
|
## 0.20.7
|
|
94
117
|
- fix: ship compiled OpenCode plugin bundle
|
|
95
118
|
- fix: always show model label in tool.execute.after footer, even with zero savings
|
|
96
|
-
- fix: always show model label in tool.execute.after footer, even with zero savings
|
|
97
119
|
- fix: restore release tarball pack step
|
|
98
|
-
Merge pull request #74 from DrunkkToys/
|
|
99
|
-
Merge pull request #72 from DrunkkToys/
|
|
120
|
+
Merge pull request #74 from DrunkkToys/feature/release-live-bundle
|
|
121
|
+
Merge pull request #72 from DrunkkToys/feature/alpha-token-install-validation
|
|
100
122
|
|
|
101
123
|
|
|
102
124
|
## 0.20.6
|
|
@@ -113,17 +135,17 @@ Merge pull request #72 from DrunkkToys/codex/alpha-token-install-validation
|
|
|
113
135
|
- fix: prefer valid api tokens over placeholder env
|
|
114
136
|
- fix: gate footer stderr by runtime
|
|
115
137
|
- fix: quiet footer stderr noise
|
|
116
|
-
Merge pull request #70 from DrunkkToys/
|
|
138
|
+
Merge pull request #70 from DrunkkToys/feature/alpha-token-kill-switch
|
|
117
139
|
|
|
118
140
|
|
|
119
141
|
## 0.20.3
|
|
120
142
|
- fix: embed valid alpha token fallback
|
|
121
|
-
Merge pull request #69 from DrunkkToys/
|
|
143
|
+
Merge pull request #69 from DrunkkToys/feature/alpha-token-release
|
|
122
144
|
|
|
123
145
|
|
|
124
146
|
## 0.20.2
|
|
125
147
|
- fix: restore embedded api token fallback
|
|
126
|
-
Merge pull request #68 from DrunkkToys/
|
|
148
|
+
Merge pull request #68 from DrunkkToys/feature/embed-api-token
|
|
127
149
|
|
|
128
150
|
|
|
129
151
|
## 0.20.1
|
|
@@ -132,8 +154,8 @@ Merge pull request #68 from DrunkkToys/codex/embed-api-token
|
|
|
132
154
|
|
|
133
155
|
## 0.20.0
|
|
134
156
|
- fix: resolve live OpenCode model and refresh README launch copy
|
|
135
|
-
Merge pull request #61 from DrunkkToys/
|
|
136
|
-
Merge pull request #59 from DrunkkToys/
|
|
157
|
+
Merge pull request #61 from DrunkkToys/feature/release-candidate-blackbox-footer
|
|
158
|
+
Merge pull request #59 from DrunkkToys/feature/fix-thinking-directive-precedence
|
|
137
159
|
|
|
138
160
|
|
|
139
161
|
## 0.19.9
|
|
@@ -147,9 +169,9 @@ Fix local blackbox tracker hydration
|
|
|
147
169
|
Add model refresh silence regression test
|
|
148
170
|
Fix blackbox session context
|
|
149
171
|
Fix thinking directive precedence
|
|
150
|
-
Merge pull request #58 from DrunkkToys/
|
|
172
|
+
Merge pull request #58 from DrunkkToys/feature/fix-home-context
|
|
151
173
|
Fix session state home context
|
|
152
|
-
Merge pull request #57 from DrunkkToys/
|
|
174
|
+
Merge pull request #57 from DrunkkToys/feature/fix-opencode-launch-config
|
|
153
175
|
Fix OpenCode launch config
|
|
154
176
|
|
|
155
177
|
|
|
@@ -157,9 +179,9 @@ Fix OpenCode launch config
|
|
|
157
179
|
- feat: use native opencode model lists
|
|
158
180
|
- fix: make OpenCode footer agnostic
|
|
159
181
|
- fix: make vibeOS compatibility paths dynamic
|
|
160
|
-
Merge pull request #56 from DrunkkToys/
|
|
161
|
-
Merge pull request #55 from DrunkkToys/
|
|
162
|
-
merge: origin/master into
|
|
182
|
+
Merge pull request #56 from DrunkkToys/feature/agnostic-opencode-release
|
|
183
|
+
Merge pull request #55 from DrunkkToys/feature/agnostic-opencode-models
|
|
184
|
+
merge: origin/master into feature/agnostic-opencode-models
|
|
163
185
|
Bootstrap trinity tiers from OpenCode model
|
|
164
186
|
|
|
165
187
|
|
|
@@ -184,7 +206,7 @@ Bootstrap trinity tiers from OpenCode model
|
|
|
184
206
|
- fix: make README and runtime self-contained
|
|
185
207
|
- test: add 59 integration + e2e tests for cross-module behavior and user workflows
|
|
186
208
|
Merge pull request #49 from DrunkkToys/oc-desktop-live-savings-refresh
|
|
187
|
-
Merge pull request #48 from DrunkkToys/
|
|
209
|
+
Merge pull request #48 from DrunkkToys/feature/live-savings-refresh
|
|
188
210
|
Invalidate savings cache on state writes
|
|
189
211
|
|
|
190
212
|
|
|
@@ -194,7 +216,7 @@ Invalidate savings cache on state writes
|
|
|
194
216
|
- fix: stress mitigation directive uses raw stress score, not API-scaled
|
|
195
217
|
- fix: _refreshModel respects project-local opencode.json over bootstrap default slot
|
|
196
218
|
- docs: mark v0.19.0 as alpha milestone release
|
|
197
|
-
Merge pull request #47 from DrunkkToys/
|
|
219
|
+
Merge pull request #47 from DrunkkToys/feature/status-lock-backend-fix
|
|
198
220
|
Expose status lock and backend state
|
|
199
221
|
Fix stress mitigation and TDD smoke coverage
|
|
200
222
|
Rebuild bundle after telemetry merge
|
|
@@ -223,14 +245,12 @@ Fix budget-first mode and stabilize tests
|
|
|
223
245
|
- fix: trinity slots now authoritative over opencode.json model
|
|
224
246
|
|
|
225
247
|
## 0.18.5
|
|
226
|
-
- fix: trinity slots now authoritative over opencode.json model
|
|
227
248
|
|
|
228
249
|
|
|
229
250
|
## 0.18.4
|
|
230
251
|
- fix: quality tracking now computes avg from lifetime score/count instead of hardcoding 0
|
|
231
252
|
- fix: savings rate shown with 4 decimal precision (was rounding to $0.00/hr)
|
|
232
253
|
- fix: cache savings minimum enforced at $0.0001 per scratchpad hit (was rounding to $0)
|
|
233
|
-
- fix: ledger reconciliation flushes buffer before reading + uses Math.max() to prevent state drops
|
|
234
254
|
- fix: model lock no longer overridden by bogus opencode.json model
|
|
235
255
|
|
|
236
256
|
## 0.18.3
|
|
@@ -247,7 +267,6 @@ Fix budget-first mode and stabilize tests
|
|
|
247
267
|
|
|
248
268
|
## 0.16.0
|
|
249
269
|
- feat: dopamine-style footer + natural language system directives
|
|
250
|
-
- feat: dopamine-style footer + natural language system directives
|
|
251
270
|
- feat: turn-aware compaction directive at turn 7+
|
|
252
271
|
- feat: add forensic/web-research modes + 1084-datapoint benchmark
|
|
253
272
|
- fix: flash icon only when API connected, unified [VIBE→MODE⚡] format
|
|
@@ -289,7 +308,6 @@ footer: TDD tag controlled by blackbox, VIBE replaces AUTO
|
|
|
289
308
|
|
|
290
309
|
## 0.15.10
|
|
291
310
|
- fix: prevent empty footer from message.updated blocking text.complete
|
|
292
|
-
- fix: prevent empty footer from message.updated blocking text.complete
|
|
293
311
|
- fix: deploy copies .env.production alongside plugin
|
|
294
312
|
|
|
295
313
|
|
|
@@ -322,7 +340,6 @@ Build: self-contained bundle (vibeOScore resolved)
|
|
|
322
340
|
- fix: remove sticky fallback flag that kills auto mode after single API failure
|
|
323
341
|
- refactor: architecture simplification and scale readiness
|
|
324
342
|
- docs: update vibeOS skills to match current plugin behavior
|
|
325
|
-
- docs: update vibeOS skills to match current plugin behavior
|
|
326
343
|
- chore: finalize cleanup
|
|
327
344
|
- chore: update import paths for vibeOScore monorepo migration
|
|
328
345
|
Merge pull request #32 from DrunkkToys/refactor/architecture-simplify-scale
|
|
@@ -365,15 +382,6 @@ Merge pull request #21 from DrunkkToys/fix/api-token-and-blackbox-control-vector
|
|
|
365
382
|
|
|
366
383
|
|
|
367
384
|
## 0.14.4
|
|
368
|
-
- fix: add contents:write permission to release workflow
|
|
369
|
-
- fix: add test:ci script for fast unit tests, separate from integration tests
|
|
370
|
-
- fix: configure git identity in release workflow
|
|
371
|
-
- fix: exclude slow delegation enforcer test from npm test
|
|
372
|
-
- fix: increase test-timeout to 120s for slow delegation enforcer test
|
|
373
|
-
- fix: exclude dashboard test from test suite and add --test-timeout=60000
|
|
374
|
-
- fix: add --test-timeout=60000 to prevent cancelledByParent test failures in CI
|
|
375
|
-
- fix: exclude dashboard from tsconfig to resolve CI build failure
|
|
376
|
-
- fix: update API token and add blackboxControlVector client method
|
|
377
385
|
Merge pull request #24 from DrunkkToys/fix/ci-test-exclude-dashboard
|
|
378
386
|
Merge pull request #23 from DrunkkToys/fix/ci-test-timeout
|
|
379
387
|
Merge pull request #22 from DrunkkToys/fix/ci-exclude-dashboard
|
|
@@ -533,7 +541,6 @@ Merge pull request #21 from DrunkkToys/fix/api-token-and-blackbox-control-vector
|
|
|
533
541
|
- fix: resolution-tracker thresholds - isConverging >=0.5, detectLoop Jaccard 0.6, isRefining >-0.01
|
|
534
542
|
- perf: conditional directive injection — skip TDD/FLOW/orchestrator when control vector signals relaxed mode
|
|
535
543
|
- refactor: merge extracted modules into src/index.ts (6656→1061 lines)
|
|
536
|
-
- refactor: extract 16 modules (7207 lines) from src/index.ts into src/lib/
|
|
537
544
|
- refactor: swap blackbox import to LocalBlackboxStub (forensic)
|
|
538
545
|
- refactor: blackbox moved to API-server-only — plugin uses local stub
|
|
539
546
|
- refactor: rename CodeX MCP server to vibeOS MCP server
|
|
@@ -561,74 +568,6 @@ test api put
|
|
|
561
568
|
|
|
562
569
|
|
|
563
570
|
## 0.13.3
|
|
564
|
-
- feat: blackbox dynamically controls thinking mode per sub-regime for cost savings
|
|
565
|
-
- feat: complete remote API migration — dual-path scoreStress, patternsObserve/Record, TDD exports with local fallback + neutral env test
|
|
566
|
-
- feat: complete remote API migration — dual-path scoreStress, patternsObserve/Record, TDD exports with local fallback
|
|
567
|
-
- feat: blackbox ML enhancements — real features, loop prevention, pivot detection, outcome tracking, calibration
|
|
568
|
-
- feat: v0.10.0 — 6 enhancement phases implemented
|
|
569
|
-
- feat: WordPress integration - atomic seat+token creation
|
|
570
|
-
- feat: Phase 2 - Integrate remote API client into plugin runtime
|
|
571
|
-
- feat: Phase 1 - Remote API server for protected algorithms
|
|
572
|
-
- feat: CodeX MCP server and dashboard sidebar plugin integration
|
|
573
|
-
- feat: vibeOS TUI dashboard sidebar plugin
|
|
574
|
-
- fix: release.mjs — add missing closing brace for deploy else block
|
|
575
|
-
- fix: stabilize refactored modules — ES module bindings, setters, missing imports
|
|
576
|
-
- fix: flow-enforcer race condition, blackbox default ON, dynamic footer
|
|
577
|
-
- fix: lock model name, enforcement logging, TDD framework detection, cache display rounding
|
|
578
|
-
- fix: validateState sessions object, remove stale report writes, drop dead code
|
|
579
|
-
- fix: state validation, flow TODO dedup, session checkpointing, fetch verification
|
|
580
|
-
- fix: _appendFooter full model names, → arrow, inline stress; 361/362 pass
|
|
581
|
-
- fix: atomic state writes, safeJsonParse in flow-enforcer, hook error handling (#15)
|
|
582
|
-
- fix: model split always shown, stress inline in footer, not separate line
|
|
583
|
-
- fix: footer uses slot model name, → arrow, inline stress always, remove session-report writes, disable blackbox default
|
|
584
|
-
- fix: sync second footer builder in tool.execute.after with new template
|
|
585
|
-
- fix: compact footer with inline stress gauge, full model names, robust test assertions
|
|
586
|
-
- fix: footer uses trinity tier model name, all 362 tests pass
|
|
587
|
-
- fix: resolve pricing cache corruption, improve TODO extraction, and tune delegation savings
|
|
588
|
-
- fix: use dynamic mcp port fallback
|
|
589
|
-
- fix: handle mcp server close-reopen race
|
|
590
|
-
- fix: await mcp server startup
|
|
591
|
-
- fix: harden prompt send and unblock typecheck
|
|
592
|
-
- fix: sync opencode.json model with brain tier, restore footer icons (trend arrows, stress gauge)
|
|
593
|
-
- fix: deploy script missing vibeOS-api-server/ directory
|
|
594
|
-
- fix: footer prepended to output.output, fix tests, remove stale vibeOS/ directory
|
|
595
|
-
- fix: migrate footer from context-polluting text.complete to UI-only output.title
|
|
596
|
-
- fix: restore experimental.text.complete and message.updated hooks lost during stash
|
|
597
|
-
- fix: ensure model-tiers.json is created when no model is detected
|
|
598
|
-
- fix: update trinity status test for new dashboard format
|
|
599
|
-
- fix: compute cache savings from actual file size, remove /bin/zsh.001 floor, fix state corruption from flow_warns overwrite
|
|
600
|
-
- fix: add proper named export for auto-discovery, fix function closure
|
|
601
|
-
- fix: add startup toast to verify TUI plugin function execution
|
|
602
|
-
- fix: add auto-activation to sync script, add sidebar widget diagnostics
|
|
603
|
-
- fix: restore vibeOS sidebar dashboard widget, fix plugin path in opencode config
|
|
604
|
-
- fix: add size guard to readJsonOrEmpty to prevent OOM on massive state files
|
|
605
|
-
- fix: add generation counter + concurrent-write detection to updateState
|
|
606
|
-
- fix: dedup double footer from competing message.updated / text.complete hooks
|
|
607
|
-
- fix: append ledger entry in recordSaving() and recordCacheSaving()
|
|
608
|
-
- fix: make MCP server close() async, export closeMcpServer for test cleanup
|
|
609
|
-
- fix: isolate tests from real config (chdir sandbox, VIBEOS_MCP_PORT=0, HOME cleanup)
|
|
610
|
-
- fix: release/deploy synced lib deps - blackbox missing caused footer (and all hooks) to disappear
|
|
611
|
-
- fix: resolution-tracker thresholds - isConverging >=0.5, detectLoop Jaccard 0.6, isRefining >-0.01
|
|
612
|
-
- perf: conditional directive injection — skip TDD/FLOW/orchestrator when control vector signals relaxed mode
|
|
613
|
-
- refactor: merge extracted modules into src/index.ts (6656→1061 lines)
|
|
614
|
-
- refactor: extract 16 modules (7207 lines) from src/index.ts into src/lib/
|
|
615
|
-
- refactor: swap blackbox import to LocalBlackboxStub (forensic)
|
|
616
|
-
- refactor: blackbox moved to API-server-only — plugin uses local stub
|
|
617
|
-
- refactor: rename CodeX MCP server to vibeOS MCP server
|
|
618
|
-
- docs: add final stabilization campaign report (#14)
|
|
619
|
-
- docs: add stabilization audit reports for sessions 02-06 and 09 (#13)
|
|
620
|
-
- docs: add stabilization baseline report (#12)
|
|
621
|
-
- docs: update README and AGENTS for remote API protection (Phase 1+2)
|
|
622
|
-
- docs: fix brand name, update AGENTS line count, document shell.env hook
|
|
623
|
-
- docs: update README and AGENTS for v0.9.1 features
|
|
624
|
-
- test: add cross-session restart E2E test (BUG 10)
|
|
625
|
-
- chore: remove TDD auto-generated test artifacts
|
|
626
|
-
- chore: hardcode public VIBEOS_API_TOKEN as default
|
|
627
|
-
- chore: bump to 0.11.0 — blackbox ML engine, loop prevention, pivot detection, API-only architecture
|
|
628
|
-
- chore: replace diagnostic log with visible toast
|
|
629
|
-
- chore: add secrets to .gitignore (.env.production, PRODUCTION-CREDENTIALS.md)
|
|
630
|
-
- ci: add vibeOS test workflow
|
|
631
|
-
- chore: v0.9.1
|
|
632
571
|
bump 0.13.2 — state.ts stub exports, fix ESM import errors
|
|
633
572
|
bump 0.13.1 — trinity optimize (5 modes + auto), compaction every 10 turns, state.ts stub exports
|
|
634
573
|
Merge pull request #18 from DrunkkToys/revert/low-value-api-migration
|
|
@@ -657,11 +596,7 @@ test api put
|
|
|
657
596
|
## 0.13.0
|
|
658
597
|
|
|
659
598
|
- refactor: extract 16 modules from src/index.ts into src/lib/ (state, pricing, trinity, TDD, hooks, reporting, research-audit, api-client, credit-api, turn-classify, index-helpers)
|
|
660
|
-
- feat: blackbox dynamically controls thinking mode per sub-regime for cost savings
|
|
661
599
|
- fix: flow-enforcer race condition, blackbox default ON, dynamic footer improvement
|
|
662
|
-
- fix: lock model name, enforcement logging, TDD framework detection, cache display rounding
|
|
663
|
-
- perf: conditional directive injection — skip TDD/FLOW/orchestrator when control vector signals relaxed mode
|
|
664
|
-
- fix: model split always shown, stress inline in footer, not separate line
|
|
665
600
|
- fix: atomic state writes, safeJsonParse in flow-enforcer, hook error handling
|
|
666
601
|
- perf: inline stress in footer, remove session-report writes, disable blackbox default
|
|
667
602
|
- docs: AGENTS.md updated — 8 hooks (added session.compacting), new src/lib/ architecture
|
|
@@ -715,7 +650,6 @@ test api put
|
|
|
715
650
|
|
|
716
651
|
## 0.9.1
|
|
717
652
|
- feat: vibeOS MCP server HTTP API
|
|
718
|
-
- feat: vibeOS TUI dashboard sidebar plugin
|
|
719
653
|
- chore: sync-ts-build and flow-enforcer enhancements
|
|
720
654
|
|
|
721
655
|
## 0.9.0
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vibeostheog",
|
|
3
|
-
"version": "0.22.
|
|
3
|
+
"version": "0.22.16",
|
|
4
4
|
"description": "Cost-aware delegation enforcer for OpenCode. Tracks model usage, routes Task subagents to cheaper tiers, surfaces cumulative savings in chat. Includes research audit, reporting framework, project memory, progressive scratchpad decadence, and trinity CLI for brain/medium/cheap slot switching.",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"release": "node scripts/release.mjs",
|
|
@@ -16,10 +16,10 @@
|
|
|
16
16
|
"ts:audit": "node scripts/ts-audit.mjs",
|
|
17
17
|
"test": "VIBEOS_MCP_PORT=0 node --test --test-timeout=240000 tests/deep_integration.test.mjs tests/production_regressions.test.mjs tests/release_hardening_tigerteam.test.mjs tests/test_api_migration.neutral.test.mjs tests/test_const_assignment_regression.test.mjs tests/test_delegation_enforcer.test.mjs tests/test_diagnose_cmd.test.mjs tests/test_install_and_recovery.test.mjs tests/test_internals_stress_patterns_offtopic.test.mjs tests/test_saveos_e2e_cleanup.test.mjs tests/test_tdd_enforcer.test.mjs tests/test_10fixes_regression.test.mjs tests/test_cross_session_regression.test.mjs tests/test_mega_all_fixes.test.mjs tests/test_smart_cache_regression.test.mjs src/tests/*.test.js src/utils/tests/*.test.mjs \"src/vibeOS-lib/tests/auto-select-mode.test.mjs\" \"src/vibeOS-lib/tests/blackbox-regression.test.mjs\" \"src/vibeOS-lib/tests/blackbox-smoke.test.mjs\" \"src/vibeOS-lib/tests/budget-first-mode.test.mjs\" \"src/vibeOS-lib/tests/flow-enforcer.test.mjs\" \"src/vibeOS-lib/tests/flow-secrets.test.mjs\" \"src/vibeOS-lib/tests/session-metrics.test.mjs\" \"src/vibeOS-lib/tests/test_stress.test.mjs\"",
|
|
18
18
|
"test:ci": "VIBEOS_MCP_PORT=0 node --test --test-timeout=30000 tests/production_regressions.test.mjs tests/release_hardening_tigerteam.test.mjs tests/test_const_assignment_regression.test.mjs tests/test_diagnose_cmd.test.mjs tests/test_install_and_recovery.test.mjs tests/test_saveos_e2e_cleanup.test.mjs tests/test_tdd_enforcer.test.mjs tests/test_10fixes_regression.test.mjs tests/test_cross_session_regression.test.mjs tests/test_mega_all_fixes.test.mjs tests/test_smart_cache_regression.test.mjs src/tests/*.test.js src/utils/tests/*.test.mjs \"src/vibeOS-lib/tests/auto-select-mode.test.mjs\" \"src/vibeOS-lib/tests/blackbox-regression.test.mjs\" \"src/vibeOS-lib/tests/blackbox-smoke.test.mjs\" \"src/vibeOS-lib/tests/budget-first-mode.test.mjs\" \"src/vibeOS-lib/tests/flow-enforcer.test.mjs\" \"src/vibeOS-lib/tests/flow-secrets.test.mjs\" \"src/vibeOS-lib/tests/session-metrics.test.mjs\" \"src/vibeOS-lib/tests/test_stress.test.mjs\"",
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"
|
|
22
|
-
"
|
|
19
|
+
"guard": "bash plugins/vibetheog-guard/scripts/run-guard.sh",
|
|
20
|
+
"guard:full": "VIBETHEOG_GUARD_FULL=1 bash plugins/vibetheog-guard/scripts/run-guard.sh",
|
|
21
|
+
"hook:precommit": "bash plugins/vibetheog-guard/hooks/pre-commit.sh",
|
|
22
|
+
"hook:summary": "bash plugins/vibetheog-guard/hooks/post-command-summary.sh",
|
|
23
23
|
"precommit": "node scripts/pre-commit.mjs",
|
|
24
24
|
"audit-state": "node scripts/audit-state.mjs",
|
|
25
25
|
"migrate-ledger": "node scripts/migrate-ledger.mjs",
|
package/src/lib/api-client.js
CHANGED
|
@@ -535,6 +535,10 @@ export function setApiToken(newToken) {
|
|
|
535
535
|
persistPrimaryApiEnvState({ token: VIBEOS_API_TOKEN, disabled: false });
|
|
536
536
|
if (_anomalyDetector)
|
|
537
537
|
_anomalyDetector.reset();
|
|
538
|
+
_apiClient = null;
|
|
539
|
+
_apiFallbackMode = false;
|
|
540
|
+
_apiFallbackSince = null;
|
|
541
|
+
resetApiConnection();
|
|
538
542
|
console.error("[vibeOS] API token updated via setApiToken");
|
|
539
543
|
}
|
|
540
544
|
catch (e) {
|
|
@@ -671,6 +675,8 @@ function syncApiTokenFromDisk() {
|
|
|
671
675
|
VIBEOS_API_DISABLED = false;
|
|
672
676
|
VIBEOS_API_TOKEN ||= EMBEDDED_API_TOKEN;
|
|
673
677
|
VIBEOS_API_ENABLED = process.env.VIBEOS_API_ENABLED !== "false" && (!!VIBEOS_API_TOKEN || !!VIBEOS_API_BOOTSTRAP_TOKEN);
|
|
678
|
+
_apiFallbackMode = false;
|
|
679
|
+
_apiFallbackSince = null;
|
|
674
680
|
}
|
|
675
681
|
}
|
|
676
682
|
export function getApiClient() {
|
package/src/lib/hooks/footer.js
CHANGED
|
@@ -201,7 +201,12 @@ async function _appendFooter(input, output, directory) {
|
|
|
201
201
|
liveModel = readConfig(directory) || readConfig(join(process.env.HOME || "", ".config", "opencode")) || process?.env?.OPENCODE_MODEL || "";
|
|
202
202
|
}
|
|
203
203
|
const displayModel = resolveDisplayModelId(liveModel || brainModel || currentModel || "", directory) || liveModel || brainModel || currentModel;
|
|
204
|
-
const
|
|
204
|
+
const resolvedModel = displayModel || liveModel || brainModel || currentModel || "";
|
|
205
|
+
if (resolvedModel && resolvedModel !== currentModel) {
|
|
206
|
+
setCurrentModel(resolvedModel);
|
|
207
|
+
setCurrentTier(classify(resolvedModel));
|
|
208
|
+
}
|
|
209
|
+
const execution = resolveExecutionIdentity(input?.args?.model || resolvedModel || "", directory);
|
|
205
210
|
let modelTag = `[${shortModelName(displayModel)}]`;
|
|
206
211
|
const _workerModel = slot === "brain" ? TRINITY_MEDIUM : null;
|
|
207
212
|
const totalTurns = (sesModelTurns?.brain || 0) + (sesModelTurns?.worker || 0);
|
|
@@ -215,21 +220,21 @@ async function _appendFooter(input, output, directory) {
|
|
|
215
220
|
saveReport({
|
|
216
221
|
type: "session",
|
|
217
222
|
summary: "Session cost: $" + formatUsd(ltCost) + " | cache saved: $" + formatUsd(ltCache) + " | delegation saved: $" + formatUsd(Number(sesTasks || 0)) + " | task delegations: " + Number(sesTaskDelegations || 0),
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
+
metrics: {
|
|
224
|
+
sessionId: _OC_SID,
|
|
225
|
+
projectFingerprint: currentProjectFingerprint || "unknown",
|
|
226
|
+
projectName: currentProjectName || "unknown",
|
|
227
|
+
sessionCost: ltCost,
|
|
223
228
|
cacheSavings: ltCache,
|
|
224
229
|
delegationSavingsUsd: sesTasks,
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
230
|
+
taskDelegationCount: sesTaskDelegations,
|
|
231
|
+
// Backward compatibility (legacy field historically misnamed)
|
|
232
|
+
tasksDelegated: sesTaskDelegations,
|
|
233
|
+
model: resolvedModel || currentModel,
|
|
234
|
+
slot: loadSelection().active_slot || "unknown",
|
|
235
|
+
editSavings: sesEdit,
|
|
236
|
+
creditSavings: sesCredit,
|
|
237
|
+
context7Savings: sesC7,
|
|
233
238
|
quotaSavings: sesQuota,
|
|
234
239
|
},
|
|
235
240
|
tags: ["auto", "cost"],
|
|
@@ -684,7 +684,12 @@ export const onToolExecuteAfter = async (input, output) => {
|
|
|
684
684
|
liveModel = readConfig(projectDirectory) || readConfig(join(process.env.HOME || "", ".config", "opencode")) || process?.env?.OPENCODE_MODEL || "";
|
|
685
685
|
}
|
|
686
686
|
const displayModel = resolveDisplayModelId(liveModel || currentModel || "", projectDirectory) || liveModel || currentModel;
|
|
687
|
-
const
|
|
687
|
+
const resolvedModel = displayModel || liveModel || currentModel || "";
|
|
688
|
+
if (resolvedModel && resolvedModel !== currentModel) {
|
|
689
|
+
setCurrentModel(resolvedModel);
|
|
690
|
+
setCurrentTier(classify(resolvedModel));
|
|
691
|
+
}
|
|
692
|
+
const execution = resolveExecutionIdentity(input?.args?.model || resolvedModel || "", projectDirectory);
|
|
688
693
|
_footerText = `— ${flashIcon ? `${flashIcon} ` : ""}Quality: ${formatQualityName(execution.quality)} | Provider: ${formatProviderName(execution.provider)} | Model: ${execution.model}`;
|
|
689
694
|
if (ltTotal > 0) {
|
|
690
695
|
_footerText += ` | $${formatUsd(ltTotal)} saved`;
|
|
@@ -705,7 +710,7 @@ export const onToolExecuteAfter = async (input, output) => {
|
|
|
705
710
|
if (_autoReportCount % 5 === 0 && ltTotal > 0) {
|
|
706
711
|
saveReport({
|
|
707
712
|
type: "session", summary: `Session cost: $${formatUsd(ltCost)} | cache saved: $${formatUsd(ltCache)} | delegation saved: $${formatUsd(ltTasks)}`,
|
|
708
|
-
metrics: { sessionId: _OC_SID, sessionCost: ltCost, cacheSavings: ltCache, delegationSavingsUsd: ltTasks, model: currentModel, slot: selNow.active_slot || "unknown" },
|
|
713
|
+
metrics: { sessionId: _OC_SID, sessionCost: ltCost, cacheSavings: ltCache, delegationSavingsUsd: ltTasks, model: resolvedModel || currentModel, slot: selNow.active_slot || "unknown" },
|
|
709
714
|
tags: ["auto", "cost"],
|
|
710
715
|
});
|
|
711
716
|
}
|
package/src/lib/state.js
CHANGED
|
@@ -831,6 +831,53 @@ function indexAppend(hash, tool, size, extra) {
|
|
|
831
831
|
}
|
|
832
832
|
// ── Scratchpad hit detection ─────────────────────────────────────────
|
|
833
833
|
const scratchpadHitsSeen = new Set();
|
|
834
|
+
function scanRecentScratchpad(dir, titleCase, maxScan = 2000) {
|
|
835
|
+
try {
|
|
836
|
+
if (!existsSync(dir))
|
|
837
|
+
return null;
|
|
838
|
+
const entries = readdirSync(dir);
|
|
839
|
+
const ptrFiles = entries.filter(e => e.endsWith(".ptr"));
|
|
840
|
+
const ptrCandidates = [];
|
|
841
|
+
for (const pf of ptrFiles) {
|
|
842
|
+
if (ptrCandidates.length >= 50)
|
|
843
|
+
break;
|
|
844
|
+
try {
|
|
845
|
+
const st = statSync(join(dir, pf));
|
|
846
|
+
ptrCandidates.push({ ptrPath: join(dir, pf), mtimeMs: st.mtimeMs });
|
|
847
|
+
}
|
|
848
|
+
catch { }
|
|
849
|
+
}
|
|
850
|
+
ptrCandidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
851
|
+
let scanned = 0;
|
|
852
|
+
for (const { ptrPath } of ptrCandidates) {
|
|
853
|
+
if (scanned++ >= maxScan)
|
|
854
|
+
break;
|
|
855
|
+
try {
|
|
856
|
+
const ptrData = safeJsonParse(readFileSync(ptrPath, "utf-8"));
|
|
857
|
+
if (!ptrData?.contentHash)
|
|
858
|
+
continue;
|
|
859
|
+
const ptrTool = typeof ptrData.tool === "string" ? (TOOL_NAME_NORMALIZE[ptrData.tool] || ptrData.tool) : null;
|
|
860
|
+
if (titleCase && ptrTool && ptrTool !== titleCase)
|
|
861
|
+
continue;
|
|
862
|
+
const contentHash = String(ptrData.contentHash);
|
|
863
|
+
const f = join(dir, `${contentHash}.txt`);
|
|
864
|
+
if (!existsSync(f))
|
|
865
|
+
continue;
|
|
866
|
+
const st = statSync(f);
|
|
867
|
+
const ageSec = (Date.now() - st.mtimeMs) / 1000;
|
|
868
|
+
if (ageSec > SCRATCHPAD_MAX_AGE_SEC)
|
|
869
|
+
continue;
|
|
870
|
+
const sumPath = join(dir, `${contentHash}.summary.txt`);
|
|
871
|
+
return { hash: contentHash, fullPath: f, sizeBytes: st.size, ageSec: Math.round(ageSec), summaryPath: existsSync(sumPath) ? sumPath : null };
|
|
872
|
+
}
|
|
873
|
+
catch { }
|
|
874
|
+
}
|
|
875
|
+
return null;
|
|
876
|
+
}
|
|
877
|
+
catch {
|
|
878
|
+
return null;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
834
881
|
function getScratchpadHit(toolLower, args, baseDir = null) {
|
|
835
882
|
if (!SCRATCHPAD_TOOLS.has(toolLower))
|
|
836
883
|
return null;
|
|
@@ -838,15 +885,12 @@ function getScratchpadHit(toolLower, args, baseDir = null) {
|
|
|
838
885
|
const inputJson = stableJson(args ?? {});
|
|
839
886
|
const hash = createHash("sha256").update(`${titleCase}\n${inputJson}\n`).digest("hex").slice(0, 16);
|
|
840
887
|
const sessionDir = baseDir || getSessionScratchpadDir();
|
|
841
|
-
const globalDir = SCRATCHPAD_GLOBAL_DIR;
|
|
842
888
|
const sessionPath = join(sessionDir, `${hash}.txt`);
|
|
843
|
-
|
|
844
|
-
let fullPath = existsSync(globalPath) ? globalPath : (existsSync(sessionPath) ? sessionPath : null);
|
|
889
|
+
let fullPath = existsSync(sessionPath) ? sessionPath : null;
|
|
845
890
|
if (!fullPath) {
|
|
846
891
|
// Try pointer files (created by compressToolOutputs mapping input hash -> content hash)
|
|
847
892
|
const ptrSessionPath = join(sessionDir, `${hash}.ptr`);
|
|
848
|
-
const
|
|
849
|
-
const ptrPath = existsSync(ptrSessionPath) ? ptrSessionPath : (existsSync(ptrGlobalPath) ? ptrGlobalPath : null);
|
|
893
|
+
const ptrPath = existsSync(ptrSessionPath) ? ptrSessionPath : null;
|
|
850
894
|
let resolvedHash = hash;
|
|
851
895
|
if (ptrPath) {
|
|
852
896
|
try {
|
|
@@ -854,27 +898,28 @@ function getScratchpadHit(toolLower, args, baseDir = null) {
|
|
|
854
898
|
if (ptrData?.contentHash) {
|
|
855
899
|
resolvedHash = ptrData.contentHash;
|
|
856
900
|
const rSessionPath = join(sessionDir, `${resolvedHash}.txt`);
|
|
857
|
-
|
|
858
|
-
fullPath = existsSync(rGlobalPath) ? rGlobalPath : (existsSync(rSessionPath) ? rSessionPath : null);
|
|
901
|
+
fullPath = existsSync(rSessionPath) ? rSessionPath : null;
|
|
859
902
|
}
|
|
860
903
|
}
|
|
861
904
|
catch { }
|
|
862
|
-
}
|
|
863
|
-
if (!fullPath) {
|
|
864
|
-
return null;
|
|
865
|
-
}
|
|
866
905
|
}
|
|
906
|
+
if (!fullPath) {
|
|
907
|
+
const recent = scanRecentScratchpad(sessionDir, titleCase, 2000);
|
|
908
|
+
if (recent)
|
|
909
|
+
return recent;
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
867
913
|
try {
|
|
868
914
|
const st = statSync(fullPath);
|
|
869
915
|
const ageSec = (Date.now() - st.mtimeMs) / 1000;
|
|
870
916
|
if (ageSec > SCRATCHPAD_MAX_AGE_SEC)
|
|
871
917
|
return null;
|
|
872
|
-
const
|
|
873
|
-
const
|
|
874
|
-
const summaryPath = existsSync(sessionSummaryPath) ? sessionSummaryPath : (existsSync(globalSummaryPath) ? globalSummaryPath : null);
|
|
918
|
+
const summaryPath = join(sessionDir, `${hash}.summary.txt`);
|
|
919
|
+
const finalSummary = existsSync(summaryPath) ? summaryPath : null;
|
|
875
920
|
return {
|
|
876
921
|
hash, fullPath, sizeBytes: st.size, ageSec: Math.round(ageSec),
|
|
877
|
-
summaryPath,
|
|
922
|
+
summaryPath: finalSummary,
|
|
878
923
|
};
|
|
879
924
|
}
|
|
880
925
|
catch {
|
|
@@ -1740,7 +1785,7 @@ LEDGER_BUFFER_MAX, LEDGER_BUFFER_FLUSH_MS, _ledgerBuffer, _ledgerBufferTimer, _f
|
|
|
1740
1785
|
// Stable JSON
|
|
1741
1786
|
stableJson, _readHead, indexAppend,
|
|
1742
1787
|
// Scratchpad hits
|
|
1743
|
-
scratchpadHitsSeen, getScratchpadHit, recordScratchpadObservation, _pruneScratchpadDir, runDecadenceCycle, applyDecadence, cleanupStaleSessionScratchpads, pruneScratchpadOnce,
|
|
1788
|
+
scratchpadHitsSeen, scanRecentScratchpad, getScratchpadHit, recordScratchpadObservation, _pruneScratchpadDir, runDecadenceCycle, applyDecadence, cleanupStaleSessionScratchpads, pruneScratchpadOnce,
|
|
1744
1789
|
// Active jobs
|
|
1745
1790
|
loadActiveJobs, getActiveJobForProject, saveActiveJobForProject, saveJobRecord, loadJobRecord,
|
|
1746
1791
|
// Project memory
|
|
@@ -10,6 +10,28 @@ class LocalBlackboxStub {
|
|
|
10
10
|
this.history = [];
|
|
11
11
|
this.loopCount = 0;
|
|
12
12
|
}
|
|
13
|
+
normalizeText(text) {
|
|
14
|
+
return (text || "")
|
|
15
|
+
.toLowerCase()
|
|
16
|
+
.replace(/[^a-z0-9\s]+/g, " ")
|
|
17
|
+
.replace(/\s+/g, " ")
|
|
18
|
+
.trim();
|
|
19
|
+
}
|
|
20
|
+
getRepeatStreak() {
|
|
21
|
+
if (this.history.length < 2)
|
|
22
|
+
return 0;
|
|
23
|
+
const normalizedLast = this.normalizeText(this.history[this.history.length - 1].text);
|
|
24
|
+
if (!normalizedLast)
|
|
25
|
+
return 0;
|
|
26
|
+
let streak = 1;
|
|
27
|
+
for (let i = this.history.length - 2; i >= 0; i--) {
|
|
28
|
+
const normalized = this.normalizeText(this.history[i].text);
|
|
29
|
+
if (!normalized || normalized !== normalizedLast)
|
|
30
|
+
break;
|
|
31
|
+
streak++;
|
|
32
|
+
}
|
|
33
|
+
return streak;
|
|
34
|
+
}
|
|
13
35
|
extractFeatures(text) {
|
|
14
36
|
if (!text || typeof text !== "string")
|
|
15
37
|
return {};
|
|
@@ -78,10 +100,11 @@ class LocalBlackboxStub {
|
|
|
78
100
|
const action = this.classifyAction(text);
|
|
79
101
|
const entropy = this.computeEntropy(features);
|
|
80
102
|
const uncertainty = this.computeUncertainty(features);
|
|
81
|
-
const isLooping = this.detectBasicLoop(text);
|
|
82
103
|
this.history.push({ text, timestamp: Date.now() / 1000 });
|
|
83
104
|
if (this.history.length > 10)
|
|
84
105
|
this.history.shift();
|
|
106
|
+
const repeatStreak = this.getRepeatStreak();
|
|
107
|
+
const isLooping = repeatStreak >= 2 || this.detectBasicLoop(text);
|
|
85
108
|
if (isLooping)
|
|
86
109
|
this.loopCount++;
|
|
87
110
|
else
|
|
@@ -95,7 +118,8 @@ class LocalBlackboxStub {
|
|
|
95
118
|
continuity_state: "MEDIUM",
|
|
96
119
|
is_looping: isLooping,
|
|
97
120
|
loop_consecutive: this.loopCount,
|
|
98
|
-
|
|
121
|
+
repeat_streak: repeatStreak,
|
|
122
|
+
loop_intervention_level: repeatStreak >= 3 || this.loopCount >= 3 ? "escalated" : repeatStreak >= 2 || this.loopCount >= 2 ? "assertive" : this.loopCount >= 1 ? "gentle" : "none",
|
|
99
123
|
pivot_detected: false,
|
|
100
124
|
pivot_score: 0.0,
|
|
101
125
|
outcome: null,
|
|
@@ -109,8 +133,8 @@ class LocalBlackboxStub {
|
|
|
109
133
|
detectBasicLoop(text, threshold = 0.5) {
|
|
110
134
|
if (this.history.length < 3)
|
|
111
135
|
return false;
|
|
112
|
-
const currWords = new Set(
|
|
113
|
-
const pastWords = new Set(this.history[this.history.length - 3].text
|
|
136
|
+
const currWords = new Set(this.normalizeText(text).split(/\s+/).filter(w => w.length > 3));
|
|
137
|
+
const pastWords = new Set(this.normalizeText(this.history[this.history.length - 3].text).split(/\s+/).filter(w => w.length > 3));
|
|
114
138
|
if (currWords.size === 0 || pastWords.size === 0)
|
|
115
139
|
return false;
|
|
116
140
|
const intersection = new Set([...currWords].filter(w => pastWords.has(w)));
|
|
@@ -121,11 +145,11 @@ class LocalBlackboxStub {
|
|
|
121
145
|
if (this.loopCount < 1)
|
|
122
146
|
return null;
|
|
123
147
|
const interventions = {
|
|
124
|
-
gentle: { level: "gentle", directive: "You may be repeating
|
|
125
|
-
assertive: { level: "assertive", directive: "You are stuck in a loop.
|
|
126
|
-
escalated: { level: "escalated", directive: "CRITICAL:
|
|
148
|
+
gentle: { level: "gentle", directive: "You may be repeating the same answer path — stop and restate the core question from a new angle.", resetSuggested: false },
|
|
149
|
+
assertive: { level: "assertive", directive: "You are stuck in a loop. STOP repeating the current answer path and list 3 alternative approaches.", resetSuggested: false },
|
|
150
|
+
escalated: { level: "escalated", directive: "CRITICAL: repeated loop detected. STOP the current approach entirely and SWITCH topics or reset strategy.", resetSuggested: true },
|
|
127
151
|
};
|
|
128
|
-
return this.loopCount >= 3 ? interventions.escalated : this.loopCount >= 2 ? interventions.assertive : interventions.gentle;
|
|
152
|
+
return this.getRepeatStreak() >= 3 || this.loopCount >= 3 ? interventions.escalated : this.getRepeatStreak() >= 2 || this.loopCount >= 2 ? interventions.assertive : interventions.gentle;
|
|
129
153
|
}
|
|
130
154
|
getPivotDirective() { return null; }
|
|
131
155
|
recordOutcome(_outcome) { }
|
|
@@ -270,7 +270,8 @@ function buildDirectives(cv, regime, state, action, optimizationMode) {
|
|
|
270
270
|
if (state.is_looping && state.loop_intervention_level && state.loop_intervention_level !== "none") {
|
|
271
271
|
const severity = state.loop_intervention_level === "escalated" ? "CRITICAL"
|
|
272
272
|
: state.loop_intervention_level === "assertive" ? "WARNING" : "NOTICE";
|
|
273
|
-
|
|
273
|
+
const repeatNote = state.repeat_streak >= 2 ? ` Repeated prompt streak: ${state.repeat_streak}.` : "";
|
|
274
|
+
d.push(`[loop prevention: ${severity}] The conversation may be looping — stop repeating the same answer path and try a different approach.${repeatNote} (level: ${state.loop_intervention_level})`);
|
|
274
275
|
}
|
|
275
276
|
if (optimizationMode && optimizationMode !== "balanced") {
|
|
276
277
|
d.push(`[optimization: ${optimizationMode}] Session optimization mode is "${optimizationMode}". This overrides default per-regime behavior.`);
|
|
@@ -21,6 +21,28 @@ export class ResolutionTracker {
|
|
|
21
21
|
this.outcomeHistory = [];
|
|
22
22
|
this.calibratedWeights = null;
|
|
23
23
|
}
|
|
24
|
+
normalizeText(text) {
|
|
25
|
+
return (text || "")
|
|
26
|
+
.toLowerCase()
|
|
27
|
+
.replace(/[^a-z0-9\s]+/g, " ")
|
|
28
|
+
.replace(/\s+/g, " ")
|
|
29
|
+
.trim();
|
|
30
|
+
}
|
|
31
|
+
getRepeatStreak() {
|
|
32
|
+
if (this.history.length < 2)
|
|
33
|
+
return 0;
|
|
34
|
+
const normalizedLast = this.normalizeText(this.history[this.history.length - 1].text);
|
|
35
|
+
if (!normalizedLast)
|
|
36
|
+
return 0;
|
|
37
|
+
let streak = 1;
|
|
38
|
+
for (let i = this.history.length - 2; i >= 0; i--) {
|
|
39
|
+
const normalized = this.normalizeText(this.history[i].text);
|
|
40
|
+
if (!normalized || normalized !== normalizedLast)
|
|
41
|
+
break;
|
|
42
|
+
streak++;
|
|
43
|
+
}
|
|
44
|
+
return streak;
|
|
45
|
+
}
|
|
24
46
|
update(userText, features, action, entropy, uncertainty, embedding = null) {
|
|
25
47
|
const entry = {
|
|
26
48
|
text: userText,
|
|
@@ -90,6 +112,7 @@ export class ResolutionTracker {
|
|
|
90
112
|
const entropyTrend = this.calcEntropyTrend();
|
|
91
113
|
const featureContradiction = this.calcFeatureContradiction();
|
|
92
114
|
const embeddingDelta = this.calcEmbeddingDelta();
|
|
115
|
+
const repeatStreak = this.getRepeatStreak();
|
|
93
116
|
const isLooping = this.detectLoop();
|
|
94
117
|
const intentState = this.computeIntentState();
|
|
95
118
|
const continuityState = this.continuityState(intentState);
|
|
@@ -135,9 +158,9 @@ export class ResolutionTracker {
|
|
|
135
158
|
const momentum = this.calcMomentum(entropyTrend, actionConsistency, embeddingDelta, isLooping, lastEntry.action, lastEntry.entropy);
|
|
136
159
|
let loopLevel = "none";
|
|
137
160
|
if (isLooping) {
|
|
138
|
-
if (this.loopCount >= 4)
|
|
161
|
+
if (repeatStreak >= 3 || this.loopCount >= 4)
|
|
139
162
|
loopLevel = "escalated";
|
|
140
|
-
else if (this.loopCount >= 3)
|
|
163
|
+
else if (repeatStreak >= 2 || this.loopCount >= 3)
|
|
141
164
|
loopLevel = "assertive";
|
|
142
165
|
else if (this.loopCount >= 2)
|
|
143
166
|
loopLevel = "suggestive";
|
|
@@ -165,6 +188,7 @@ export class ResolutionTracker {
|
|
|
165
188
|
continuity_state: continuityState,
|
|
166
189
|
is_looping: isLooping,
|
|
167
190
|
loop_consecutive: this.loopCount,
|
|
191
|
+
repeat_streak: repeatStreak,
|
|
168
192
|
loop_intervention_level: loopLevel,
|
|
169
193
|
pivot_detected: pivotDetected,
|
|
170
194
|
pivot_score: Math.round(pivotScore * 10000) / 10000,
|
|
@@ -224,10 +248,13 @@ export class ResolutionTracker {
|
|
|
224
248
|
detectLoop(k = 3, threshold = 0.6) {
|
|
225
249
|
const effectiveThreshold = this.calibratedWeights?.loopJaccard ?? threshold;
|
|
226
250
|
const effectiveK = this.calibratedWeights?.loopK ?? k;
|
|
251
|
+
const repeatStreak = this.getRepeatStreak();
|
|
252
|
+
if (repeatStreak >= 2)
|
|
253
|
+
return true;
|
|
227
254
|
if (this.history.length < effectiveK + 1)
|
|
228
255
|
return false;
|
|
229
|
-
const currWords = new Set(this.history[this.history.length - 1].text
|
|
230
|
-
const pastWords = new Set(this.history[this.history.length - (effectiveK + 1)].text
|
|
256
|
+
const currWords = new Set(this.normalizeText(this.history[this.history.length - 1].text).split(/\s+/).filter(Boolean));
|
|
257
|
+
const pastWords = new Set(this.normalizeText(this.history[this.history.length - (effectiveK + 1)].text).split(/\s+/).filter(Boolean));
|
|
231
258
|
if (currWords.size === 0 || pastWords.size === 0)
|
|
232
259
|
return false;
|
|
233
260
|
const intersection = new Set([...currWords].filter(w => pastWords.has(w)));
|
|
@@ -353,19 +380,19 @@ export class ResolutionTracker {
|
|
|
353
380
|
return null;
|
|
354
381
|
const interventions = {
|
|
355
382
|
gentle: {
|
|
356
|
-
directive: "You may be repeating
|
|
383
|
+
directive: "You may be repeating the same answer path — stop and restate the core question from a new angle before continuing.",
|
|
357
384
|
resetSuggested: false,
|
|
358
385
|
},
|
|
359
386
|
suggestive: {
|
|
360
|
-
directive: "The conversation is looping. Step back
|
|
387
|
+
directive: "The conversation is looping. Do not continue the same answer path. Step back, identify what new information is missing, and ask for a different constraint or approach.",
|
|
361
388
|
resetSuggested: false,
|
|
362
389
|
},
|
|
363
390
|
assertive: {
|
|
364
|
-
directive: "You are stuck in a loop.
|
|
391
|
+
directive: "You are stuck in a loop. STOP repeating the current answer path. PIVOT: list 3 alternative approaches you have not tried and choose one.",
|
|
365
392
|
resetSuggested: false,
|
|
366
393
|
},
|
|
367
394
|
escalated: {
|
|
368
|
-
directive: "CRITICAL:
|
|
395
|
+
directive: "CRITICAL: repeated loop detected. STOP the current approach entirely. Reset the strategy, SWITCH topics or scope, and do not continue the same line of reasoning.",
|
|
369
396
|
resetSuggested: true,
|
|
370
397
|
},
|
|
371
398
|
};
|