vibeostheog 0.22.6 → 0.22.9
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 +57 -105
- package/package.json +1 -1
- package/src/lib/api-client.js +6 -0
- package/src/lib/hooks/footer.js +20 -14
- package/src/lib/hooks/tool-execute.js +7 -2
- package/src/lib/state.js +19 -43
- package/src/lib/trinity-tool.js +2 -1
- 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 +41 -10
- package/src/vibeOS-lib/flow-enforcer.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,7 +1,49 @@
|
|
|
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 old branding from guard plugin
|
|
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
|
+
|
|
26
|
+
## 0.22.12
|
|
27
|
+
- fix: harden scratchpad cache
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
## 0.22.11
|
|
31
|
+
- fix: harden blackbox pivot detection and add regression coverage
|
|
32
|
+
|
|
33
|
+
## 0.22.10
|
|
34
|
+
- fix: append enforcement tags (ENF, FLOW, TDD, LOCK) to live footer
|
|
35
|
+
- fix: flow-todo-queue path inconsistency and loadRules loop bug (#105)
|
|
36
|
+
- chore: bump to 0.22.8 (#106)
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
## 0.22.8
|
|
40
|
+
- fix: flow-todo-queue path inconsistency (missing dot prefix broke trinity todo visibility)
|
|
41
|
+
- fix: getSessionFlowCounts calling loadRules() inside loop (redundant statSync per entry)
|
|
42
|
+
- chore: bump to 0.22.8
|
|
43
|
+
|
|
1
44
|
## 0.22.6
|
|
2
45
|
- feat: wire CostAnomalyDetector into tool-execute hook
|
|
3
46
|
- feat: replace TokenAnomalyDetector with CostAnomalyDetector
|
|
4
|
-
- feat: replace TokenAnomalyDetector with CostAnomalyDetector
|
|
5
47
|
- fix: bin/setup.js now delegates to deploy.mjs for proper plugin install
|
|
6
48
|
- fix: restore anomaly detector class in TS source, add mega regression tests
|
|
7
49
|
- fix: read path prefers global scratchpad over stale session-local copies
|
|
@@ -33,7 +75,6 @@ Merge pull request #91 from DrunkkToys/pr/cache-write-savings
|
|
|
33
75
|
## 0.20.15
|
|
34
76
|
- feat: dashboard blackbox telemetry — bidirectional BE/FE sync
|
|
35
77
|
- fix: mock auth and clear OPENCODE_MODEL in bootstrap test, commit blackbox .js for CI
|
|
36
|
-
- fix: mock auth and clear OPENCODE_MODEL in bootstrap test, commit blackbox .js for CI
|
|
37
78
|
- docs: fix speed mode quality rating in comparison table (#83)
|
|
38
79
|
- docs: fix token defaults in env vars table
|
|
39
80
|
- docs: update README to reflect actual features and fix inaccuracies
|
|
@@ -75,10 +116,9 @@ release: v0.20.13 — holistic CLI footer fix + regression tests (#80)
|
|
|
75
116
|
## 0.20.7
|
|
76
117
|
- fix: ship compiled OpenCode plugin bundle
|
|
77
118
|
- fix: always show model label in tool.execute.after footer, even with zero savings
|
|
78
|
-
- fix: always show model label in tool.execute.after footer, even with zero savings
|
|
79
119
|
- fix: restore release tarball pack step
|
|
80
|
-
Merge pull request #74 from DrunkkToys/
|
|
81
|
-
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
|
|
82
122
|
|
|
83
123
|
|
|
84
124
|
## 0.20.6
|
|
@@ -95,17 +135,17 @@ Merge pull request #72 from DrunkkToys/codex/alpha-token-install-validation
|
|
|
95
135
|
- fix: prefer valid api tokens over placeholder env
|
|
96
136
|
- fix: gate footer stderr by runtime
|
|
97
137
|
- fix: quiet footer stderr noise
|
|
98
|
-
Merge pull request #70 from DrunkkToys/
|
|
138
|
+
Merge pull request #70 from DrunkkToys/feature/alpha-token-kill-switch
|
|
99
139
|
|
|
100
140
|
|
|
101
141
|
## 0.20.3
|
|
102
142
|
- fix: embed valid alpha token fallback
|
|
103
|
-
Merge pull request #69 from DrunkkToys/
|
|
143
|
+
Merge pull request #69 from DrunkkToys/feature/alpha-token-release
|
|
104
144
|
|
|
105
145
|
|
|
106
146
|
## 0.20.2
|
|
107
147
|
- fix: restore embedded api token fallback
|
|
108
|
-
Merge pull request #68 from DrunkkToys/
|
|
148
|
+
Merge pull request #68 from DrunkkToys/feature/embed-api-token
|
|
109
149
|
|
|
110
150
|
|
|
111
151
|
## 0.20.1
|
|
@@ -114,8 +154,8 @@ Merge pull request #68 from DrunkkToys/codex/embed-api-token
|
|
|
114
154
|
|
|
115
155
|
## 0.20.0
|
|
116
156
|
- fix: resolve live OpenCode model and refresh README launch copy
|
|
117
|
-
Merge pull request #61 from DrunkkToys/
|
|
118
|
-
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
|
|
119
159
|
|
|
120
160
|
|
|
121
161
|
## 0.19.9
|
|
@@ -129,9 +169,9 @@ Fix local blackbox tracker hydration
|
|
|
129
169
|
Add model refresh silence regression test
|
|
130
170
|
Fix blackbox session context
|
|
131
171
|
Fix thinking directive precedence
|
|
132
|
-
Merge pull request #58 from DrunkkToys/
|
|
172
|
+
Merge pull request #58 from DrunkkToys/feature/fix-home-context
|
|
133
173
|
Fix session state home context
|
|
134
|
-
Merge pull request #57 from DrunkkToys/
|
|
174
|
+
Merge pull request #57 from DrunkkToys/feature/fix-opencode-launch-config
|
|
135
175
|
Fix OpenCode launch config
|
|
136
176
|
|
|
137
177
|
|
|
@@ -139,9 +179,9 @@ Fix OpenCode launch config
|
|
|
139
179
|
- feat: use native opencode model lists
|
|
140
180
|
- fix: make OpenCode footer agnostic
|
|
141
181
|
- fix: make vibeOS compatibility paths dynamic
|
|
142
|
-
Merge pull request #56 from DrunkkToys/
|
|
143
|
-
Merge pull request #55 from DrunkkToys/
|
|
144
|
-
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
|
|
145
185
|
Bootstrap trinity tiers from OpenCode model
|
|
146
186
|
|
|
147
187
|
|
|
@@ -166,7 +206,7 @@ Bootstrap trinity tiers from OpenCode model
|
|
|
166
206
|
- fix: make README and runtime self-contained
|
|
167
207
|
- test: add 59 integration + e2e tests for cross-module behavior and user workflows
|
|
168
208
|
Merge pull request #49 from DrunkkToys/oc-desktop-live-savings-refresh
|
|
169
|
-
Merge pull request #48 from DrunkkToys/
|
|
209
|
+
Merge pull request #48 from DrunkkToys/feature/live-savings-refresh
|
|
170
210
|
Invalidate savings cache on state writes
|
|
171
211
|
|
|
172
212
|
|
|
@@ -176,7 +216,7 @@ Invalidate savings cache on state writes
|
|
|
176
216
|
- fix: stress mitigation directive uses raw stress score, not API-scaled
|
|
177
217
|
- fix: _refreshModel respects project-local opencode.json over bootstrap default slot
|
|
178
218
|
- docs: mark v0.19.0 as alpha milestone release
|
|
179
|
-
Merge pull request #47 from DrunkkToys/
|
|
219
|
+
Merge pull request #47 from DrunkkToys/feature/status-lock-backend-fix
|
|
180
220
|
Expose status lock and backend state
|
|
181
221
|
Fix stress mitigation and TDD smoke coverage
|
|
182
222
|
Rebuild bundle after telemetry merge
|
|
@@ -205,14 +245,12 @@ Fix budget-first mode and stabilize tests
|
|
|
205
245
|
- fix: trinity slots now authoritative over opencode.json model
|
|
206
246
|
|
|
207
247
|
## 0.18.5
|
|
208
|
-
- fix: trinity slots now authoritative over opencode.json model
|
|
209
248
|
|
|
210
249
|
|
|
211
250
|
## 0.18.4
|
|
212
251
|
- fix: quality tracking now computes avg from lifetime score/count instead of hardcoding 0
|
|
213
252
|
- fix: savings rate shown with 4 decimal precision (was rounding to $0.00/hr)
|
|
214
253
|
- fix: cache savings minimum enforced at $0.0001 per scratchpad hit (was rounding to $0)
|
|
215
|
-
- fix: ledger reconciliation flushes buffer before reading + uses Math.max() to prevent state drops
|
|
216
254
|
- fix: model lock no longer overridden by bogus opencode.json model
|
|
217
255
|
|
|
218
256
|
## 0.18.3
|
|
@@ -229,7 +267,6 @@ Fix budget-first mode and stabilize tests
|
|
|
229
267
|
|
|
230
268
|
## 0.16.0
|
|
231
269
|
- feat: dopamine-style footer + natural language system directives
|
|
232
|
-
- feat: dopamine-style footer + natural language system directives
|
|
233
270
|
- feat: turn-aware compaction directive at turn 7+
|
|
234
271
|
- feat: add forensic/web-research modes + 1084-datapoint benchmark
|
|
235
272
|
- fix: flash icon only when API connected, unified [VIBE→MODE⚡] format
|
|
@@ -271,7 +308,6 @@ footer: TDD tag controlled by blackbox, VIBE replaces AUTO
|
|
|
271
308
|
|
|
272
309
|
## 0.15.10
|
|
273
310
|
- fix: prevent empty footer from message.updated blocking text.complete
|
|
274
|
-
- fix: prevent empty footer from message.updated blocking text.complete
|
|
275
311
|
- fix: deploy copies .env.production alongside plugin
|
|
276
312
|
|
|
277
313
|
|
|
@@ -304,7 +340,6 @@ Build: self-contained bundle (vibeOScore resolved)
|
|
|
304
340
|
- fix: remove sticky fallback flag that kills auto mode after single API failure
|
|
305
341
|
- refactor: architecture simplification and scale readiness
|
|
306
342
|
- docs: update vibeOS skills to match current plugin behavior
|
|
307
|
-
- docs: update vibeOS skills to match current plugin behavior
|
|
308
343
|
- chore: finalize cleanup
|
|
309
344
|
- chore: update import paths for vibeOScore monorepo migration
|
|
310
345
|
Merge pull request #32 from DrunkkToys/refactor/architecture-simplify-scale
|
|
@@ -347,15 +382,6 @@ Merge pull request #21 from DrunkkToys/fix/api-token-and-blackbox-control-vector
|
|
|
347
382
|
|
|
348
383
|
|
|
349
384
|
## 0.14.4
|
|
350
|
-
- fix: add contents:write permission to release workflow
|
|
351
|
-
- fix: add test:ci script for fast unit tests, separate from integration tests
|
|
352
|
-
- fix: configure git identity in release workflow
|
|
353
|
-
- fix: exclude slow delegation enforcer test from npm test
|
|
354
|
-
- fix: increase test-timeout to 120s for slow delegation enforcer test
|
|
355
|
-
- fix: exclude dashboard test from test suite and add --test-timeout=60000
|
|
356
|
-
- fix: add --test-timeout=60000 to prevent cancelledByParent test failures in CI
|
|
357
|
-
- fix: exclude dashboard from tsconfig to resolve CI build failure
|
|
358
|
-
- fix: update API token and add blackboxControlVector client method
|
|
359
385
|
Merge pull request #24 from DrunkkToys/fix/ci-test-exclude-dashboard
|
|
360
386
|
Merge pull request #23 from DrunkkToys/fix/ci-test-timeout
|
|
361
387
|
Merge pull request #22 from DrunkkToys/fix/ci-exclude-dashboard
|
|
@@ -515,7 +541,6 @@ Merge pull request #21 from DrunkkToys/fix/api-token-and-blackbox-control-vector
|
|
|
515
541
|
- fix: resolution-tracker thresholds - isConverging >=0.5, detectLoop Jaccard 0.6, isRefining >-0.01
|
|
516
542
|
- perf: conditional directive injection — skip TDD/FLOW/orchestrator when control vector signals relaxed mode
|
|
517
543
|
- refactor: merge extracted modules into src/index.ts (6656→1061 lines)
|
|
518
|
-
- refactor: extract 16 modules (7207 lines) from src/index.ts into src/lib/
|
|
519
544
|
- refactor: swap blackbox import to LocalBlackboxStub (forensic)
|
|
520
545
|
- refactor: blackbox moved to API-server-only — plugin uses local stub
|
|
521
546
|
- refactor: rename CodeX MCP server to vibeOS MCP server
|
|
@@ -543,74 +568,6 @@ test api put
|
|
|
543
568
|
|
|
544
569
|
|
|
545
570
|
## 0.13.3
|
|
546
|
-
- feat: blackbox dynamically controls thinking mode per sub-regime for cost savings
|
|
547
|
-
- feat: complete remote API migration — dual-path scoreStress, patternsObserve/Record, TDD exports with local fallback + neutral env test
|
|
548
|
-
- feat: complete remote API migration — dual-path scoreStress, patternsObserve/Record, TDD exports with local fallback
|
|
549
|
-
- feat: blackbox ML enhancements — real features, loop prevention, pivot detection, outcome tracking, calibration
|
|
550
|
-
- feat: v0.10.0 — 6 enhancement phases implemented
|
|
551
|
-
- feat: WordPress integration - atomic seat+token creation
|
|
552
|
-
- feat: Phase 2 - Integrate remote API client into plugin runtime
|
|
553
|
-
- feat: Phase 1 - Remote API server for protected algorithms
|
|
554
|
-
- feat: CodeX MCP server and dashboard sidebar plugin integration
|
|
555
|
-
- feat: vibeOS TUI dashboard sidebar plugin
|
|
556
|
-
- fix: release.mjs — add missing closing brace for deploy else block
|
|
557
|
-
- fix: stabilize refactored modules — ES module bindings, setters, missing imports
|
|
558
|
-
- fix: flow-enforcer race condition, blackbox default ON, dynamic footer
|
|
559
|
-
- fix: lock model name, enforcement logging, TDD framework detection, cache display rounding
|
|
560
|
-
- fix: validateState sessions object, remove stale report writes, drop dead code
|
|
561
|
-
- fix: state validation, flow TODO dedup, session checkpointing, fetch verification
|
|
562
|
-
- fix: _appendFooter full model names, → arrow, inline stress; 361/362 pass
|
|
563
|
-
- fix: atomic state writes, safeJsonParse in flow-enforcer, hook error handling (#15)
|
|
564
|
-
- fix: model split always shown, stress inline in footer, not separate line
|
|
565
|
-
- fix: footer uses slot model name, → arrow, inline stress always, remove session-report writes, disable blackbox default
|
|
566
|
-
- fix: sync second footer builder in tool.execute.after with new template
|
|
567
|
-
- fix: compact footer with inline stress gauge, full model names, robust test assertions
|
|
568
|
-
- fix: footer uses trinity tier model name, all 362 tests pass
|
|
569
|
-
- fix: resolve pricing cache corruption, improve TODO extraction, and tune delegation savings
|
|
570
|
-
- fix: use dynamic mcp port fallback
|
|
571
|
-
- fix: handle mcp server close-reopen race
|
|
572
|
-
- fix: await mcp server startup
|
|
573
|
-
- fix: harden prompt send and unblock typecheck
|
|
574
|
-
- fix: sync opencode.json model with brain tier, restore footer icons (trend arrows, stress gauge)
|
|
575
|
-
- fix: deploy script missing vibeOS-api-server/ directory
|
|
576
|
-
- fix: footer prepended to output.output, fix tests, remove stale vibeOS/ directory
|
|
577
|
-
- fix: migrate footer from context-polluting text.complete to UI-only output.title
|
|
578
|
-
- fix: restore experimental.text.complete and message.updated hooks lost during stash
|
|
579
|
-
- fix: ensure model-tiers.json is created when no model is detected
|
|
580
|
-
- fix: update trinity status test for new dashboard format
|
|
581
|
-
- fix: compute cache savings from actual file size, remove /bin/zsh.001 floor, fix state corruption from flow_warns overwrite
|
|
582
|
-
- fix: add proper named export for auto-discovery, fix function closure
|
|
583
|
-
- fix: add startup toast to verify TUI plugin function execution
|
|
584
|
-
- fix: add auto-activation to sync script, add sidebar widget diagnostics
|
|
585
|
-
- fix: restore vibeOS sidebar dashboard widget, fix plugin path in opencode config
|
|
586
|
-
- fix: add size guard to readJsonOrEmpty to prevent OOM on massive state files
|
|
587
|
-
- fix: add generation counter + concurrent-write detection to updateState
|
|
588
|
-
- fix: dedup double footer from competing message.updated / text.complete hooks
|
|
589
|
-
- fix: append ledger entry in recordSaving() and recordCacheSaving()
|
|
590
|
-
- fix: make MCP server close() async, export closeMcpServer for test cleanup
|
|
591
|
-
- fix: isolate tests from real config (chdir sandbox, VIBEOS_MCP_PORT=0, HOME cleanup)
|
|
592
|
-
- fix: release/deploy synced lib deps - blackbox missing caused footer (and all hooks) to disappear
|
|
593
|
-
- fix: resolution-tracker thresholds - isConverging >=0.5, detectLoop Jaccard 0.6, isRefining >-0.01
|
|
594
|
-
- perf: conditional directive injection — skip TDD/FLOW/orchestrator when control vector signals relaxed mode
|
|
595
|
-
- refactor: merge extracted modules into src/index.ts (6656→1061 lines)
|
|
596
|
-
- refactor: extract 16 modules (7207 lines) from src/index.ts into src/lib/
|
|
597
|
-
- refactor: swap blackbox import to LocalBlackboxStub (forensic)
|
|
598
|
-
- refactor: blackbox moved to API-server-only — plugin uses local stub
|
|
599
|
-
- refactor: rename CodeX MCP server to vibeOS MCP server
|
|
600
|
-
- docs: add final stabilization campaign report (#14)
|
|
601
|
-
- docs: add stabilization audit reports for sessions 02-06 and 09 (#13)
|
|
602
|
-
- docs: add stabilization baseline report (#12)
|
|
603
|
-
- docs: update README and AGENTS for remote API protection (Phase 1+2)
|
|
604
|
-
- docs: fix brand name, update AGENTS line count, document shell.env hook
|
|
605
|
-
- docs: update README and AGENTS for v0.9.1 features
|
|
606
|
-
- test: add cross-session restart E2E test (BUG 10)
|
|
607
|
-
- chore: remove TDD auto-generated test artifacts
|
|
608
|
-
- chore: hardcode public VIBEOS_API_TOKEN as default
|
|
609
|
-
- chore: bump to 0.11.0 — blackbox ML engine, loop prevention, pivot detection, API-only architecture
|
|
610
|
-
- chore: replace diagnostic log with visible toast
|
|
611
|
-
- chore: add secrets to .gitignore (.env.production, PRODUCTION-CREDENTIALS.md)
|
|
612
|
-
- ci: add vibeOS test workflow
|
|
613
|
-
- chore: v0.9.1
|
|
614
571
|
bump 0.13.2 — state.ts stub exports, fix ESM import errors
|
|
615
572
|
bump 0.13.1 — trinity optimize (5 modes + auto), compaction every 10 turns, state.ts stub exports
|
|
616
573
|
Merge pull request #18 from DrunkkToys/revert/low-value-api-migration
|
|
@@ -639,11 +596,7 @@ test api put
|
|
|
639
596
|
## 0.13.0
|
|
640
597
|
|
|
641
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)
|
|
642
|
-
- feat: blackbox dynamically controls thinking mode per sub-regime for cost savings
|
|
643
599
|
- fix: flow-enforcer race condition, blackbox default ON, dynamic footer improvement
|
|
644
|
-
- fix: lock model name, enforcement logging, TDD framework detection, cache display rounding
|
|
645
|
-
- perf: conditional directive injection — skip TDD/FLOW/orchestrator when control vector signals relaxed mode
|
|
646
|
-
- fix: model split always shown, stress inline in footer, not separate line
|
|
647
600
|
- fix: atomic state writes, safeJsonParse in flow-enforcer, hook error handling
|
|
648
601
|
- perf: inline stress in footer, remove session-report writes, disable blackbox default
|
|
649
602
|
- docs: AGENTS.md updated — 8 hooks (added session.compacting), new src/lib/ architecture
|
|
@@ -697,7 +650,6 @@ test api put
|
|
|
697
650
|
|
|
698
651
|
## 0.9.1
|
|
699
652
|
- feat: vibeOS MCP server HTTP API
|
|
700
|
-
- feat: vibeOS TUI dashboard sidebar plugin
|
|
701
653
|
- chore: sync-ts-build and flow-enforcer enhancements
|
|
702
654
|
|
|
703
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.9",
|
|
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",
|
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"],
|
|
@@ -291,6 +296,7 @@ async function _appendFooter(input, output, directory) {
|
|
|
291
296
|
}
|
|
292
297
|
if (modeLabel)
|
|
293
298
|
vibeLine += ` | ${formatQualityName(modeLabel)}`;
|
|
299
|
+
vibeLine += enfSuffixFooter;
|
|
294
300
|
vibeLine += ` | VIBE${flashIcon ? " ⚡" : ""}`;
|
|
295
301
|
if (_footerStress > 0.4) {
|
|
296
302
|
const stressLabel = _footerStress > 0.7 ? "high" : "elevated";
|
|
@@ -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
|
@@ -837,7 +837,6 @@ function scanRecentScratchpad(dir, titleCase, maxScan = 2000) {
|
|
|
837
837
|
return null;
|
|
838
838
|
const entries = readdirSync(dir);
|
|
839
839
|
const ptrFiles = entries.filter(e => e.endsWith(".ptr"));
|
|
840
|
-
// Try .ptr files first (created by compressToolOutputs mapping input hash -> content hash)
|
|
841
840
|
const ptrCandidates = [];
|
|
842
841
|
for (const pf of ptrFiles) {
|
|
843
842
|
if (ptrCandidates.length >= 50)
|
|
@@ -849,14 +848,18 @@ function scanRecentScratchpad(dir, titleCase, maxScan = 2000) {
|
|
|
849
848
|
catch { }
|
|
850
849
|
}
|
|
851
850
|
ptrCandidates.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
851
|
+
let scanned = 0;
|
|
852
852
|
for (const { ptrPath } of ptrCandidates) {
|
|
853
|
+
if (scanned++ >= maxScan)
|
|
854
|
+
break;
|
|
853
855
|
try {
|
|
854
856
|
const ptrData = safeJsonParse(readFileSync(ptrPath, "utf-8"));
|
|
855
857
|
if (!ptrData?.contentHash)
|
|
856
858
|
continue;
|
|
857
|
-
|
|
859
|
+
const ptrTool = typeof ptrData.tool === "string" ? (TOOL_NAME_NORMALIZE[ptrData.tool] || ptrData.tool) : null;
|
|
860
|
+
if (titleCase && ptrTool && ptrTool !== titleCase)
|
|
858
861
|
continue;
|
|
859
|
-
const contentHash = ptrData.contentHash;
|
|
862
|
+
const contentHash = String(ptrData.contentHash);
|
|
860
863
|
const f = join(dir, `${contentHash}.txt`);
|
|
861
864
|
if (!existsSync(f))
|
|
862
865
|
continue;
|
|
@@ -869,28 +872,6 @@ function scanRecentScratchpad(dir, titleCase, maxScan = 2000) {
|
|
|
869
872
|
}
|
|
870
873
|
catch { }
|
|
871
874
|
}
|
|
872
|
-
// Fallback: scan .txt files
|
|
873
|
-
const txtFiles = entries.filter(e => e.endsWith(".txt") && !e.endsWith(".summary.txt"));
|
|
874
|
-
if (txtFiles.length === 0)
|
|
875
|
-
return null;
|
|
876
|
-
const candidateHashes = [];
|
|
877
|
-
for (let i = txtFiles.length - 1; i >= 0; i--) {
|
|
878
|
-
const f = txtFiles[i];
|
|
879
|
-
if (candidateHashes.length > 50)
|
|
880
|
-
break;
|
|
881
|
-
candidateHashes.push(f.replace(/\.txt$/, ""));
|
|
882
|
-
}
|
|
883
|
-
for (const hash of candidateHashes) {
|
|
884
|
-
const f = join(dir, `${hash}.txt`);
|
|
885
|
-
if (!existsSync(f))
|
|
886
|
-
continue;
|
|
887
|
-
const st = statSync(f);
|
|
888
|
-
const ageSec = (Date.now() - st.mtimeMs) / 1000;
|
|
889
|
-
if (ageSec > SCRATCHPAD_MAX_AGE_SEC)
|
|
890
|
-
continue;
|
|
891
|
-
const sumPath = join(dir, `${hash}.summary.txt`);
|
|
892
|
-
return { hash, fullPath: f, sizeBytes: st.size, ageSec: Math.round(ageSec), summaryPath: existsSync(sumPath) ? sumPath : null };
|
|
893
|
-
}
|
|
894
875
|
return null;
|
|
895
876
|
}
|
|
896
877
|
catch {
|
|
@@ -904,15 +885,12 @@ function getScratchpadHit(toolLower, args, baseDir = null) {
|
|
|
904
885
|
const inputJson = stableJson(args ?? {});
|
|
905
886
|
const hash = createHash("sha256").update(`${titleCase}\n${inputJson}\n`).digest("hex").slice(0, 16);
|
|
906
887
|
const sessionDir = baseDir || getSessionScratchpadDir();
|
|
907
|
-
const globalDir = SCRATCHPAD_GLOBAL_DIR;
|
|
908
888
|
const sessionPath = join(sessionDir, `${hash}.txt`);
|
|
909
|
-
|
|
910
|
-
let fullPath = existsSync(globalPath) ? globalPath : (existsSync(sessionPath) ? sessionPath : null);
|
|
889
|
+
let fullPath = existsSync(sessionPath) ? sessionPath : null;
|
|
911
890
|
if (!fullPath) {
|
|
912
891
|
// Try pointer files (created by compressToolOutputs mapping input hash -> content hash)
|
|
913
892
|
const ptrSessionPath = join(sessionDir, `${hash}.ptr`);
|
|
914
|
-
const
|
|
915
|
-
const ptrPath = existsSync(ptrSessionPath) ? ptrSessionPath : (existsSync(ptrGlobalPath) ? ptrGlobalPath : null);
|
|
893
|
+
const ptrPath = existsSync(ptrSessionPath) ? ptrSessionPath : null;
|
|
916
894
|
let resolvedHash = hash;
|
|
917
895
|
if (ptrPath) {
|
|
918
896
|
try {
|
|
@@ -920,30 +898,28 @@ function getScratchpadHit(toolLower, args, baseDir = null) {
|
|
|
920
898
|
if (ptrData?.contentHash) {
|
|
921
899
|
resolvedHash = ptrData.contentHash;
|
|
922
900
|
const rSessionPath = join(sessionDir, `${resolvedHash}.txt`);
|
|
923
|
-
|
|
924
|
-
fullPath = existsSync(rGlobalPath) ? rGlobalPath : (existsSync(rSessionPath) ? rSessionPath : null);
|
|
901
|
+
fullPath = existsSync(rSessionPath) ? rSessionPath : null;
|
|
925
902
|
}
|
|
926
903
|
}
|
|
927
904
|
catch { }
|
|
928
|
-
}
|
|
929
|
-
if (!fullPath) {
|
|
930
|
-
const recent = scanRecentScratchpad(sessionDir, titleCase, 2000) || scanRecentScratchpad(globalDir, titleCase, 2000);
|
|
931
|
-
if (recent)
|
|
932
|
-
return recent;
|
|
933
|
-
return null;
|
|
934
|
-
}
|
|
935
905
|
}
|
|
906
|
+
if (!fullPath) {
|
|
907
|
+
const recent = scanRecentScratchpad(sessionDir, titleCase, 2000);
|
|
908
|
+
if (recent)
|
|
909
|
+
return recent;
|
|
910
|
+
return null;
|
|
911
|
+
}
|
|
912
|
+
}
|
|
936
913
|
try {
|
|
937
914
|
const st = statSync(fullPath);
|
|
938
915
|
const ageSec = (Date.now() - st.mtimeMs) / 1000;
|
|
939
916
|
if (ageSec > SCRATCHPAD_MAX_AGE_SEC)
|
|
940
917
|
return null;
|
|
941
|
-
const
|
|
942
|
-
const
|
|
943
|
-
const summaryPath = existsSync(sessionSummaryPath) ? sessionSummaryPath : (existsSync(globalSummaryPath) ? globalSummaryPath : null);
|
|
918
|
+
const summaryPath = join(sessionDir, `${hash}.summary.txt`);
|
|
919
|
+
const finalSummary = existsSync(summaryPath) ? summaryPath : null;
|
|
944
920
|
return {
|
|
945
921
|
hash, fullPath, sizeBytes: st.size, ageSec: Math.round(ageSec),
|
|
946
|
-
summaryPath,
|
|
922
|
+
summaryPath: finalSummary,
|
|
947
923
|
};
|
|
948
924
|
}
|
|
949
925
|
catch {
|
package/src/lib/trinity-tool.js
CHANGED
|
@@ -195,7 +195,8 @@ export function createTrinityTool(deps) {
|
|
|
195
195
|
const allEntries = [...BRANDED_MODES, ...RUNTIME_MODES];
|
|
196
196
|
const modeEntry = allEntries.find(e => e.id === slot);
|
|
197
197
|
if (modeEntry) {
|
|
198
|
-
const
|
|
198
|
+
const rawTier = modeEntry.pipeline[0] || "cheap";
|
|
199
|
+
const tierSlot = new Set(["brain", "medium", "cheap"]).has(rawTier) ? rawTier : "cheap";
|
|
199
200
|
deps.writeSelection("active_slot", tierSlot);
|
|
200
201
|
deps.writeSelection("onboarding_mode", modeEntry.tdd === "quality" || modeEntry.enforcement === "strict" ? "strict" : "assist");
|
|
201
202
|
deps.writeSelection("delegation_enforce", modeEntry.enforcement === "strict" || modeEntry.enforcement === "on");
|
|
@@ -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,
|
|
@@ -52,6 +74,10 @@ export class ResolutionTracker {
|
|
|
52
74
|
return state;
|
|
53
75
|
}
|
|
54
76
|
detectPivotSignal(current, previous) {
|
|
77
|
+
if (!current.embedding || !previous.embedding) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
const embeddingDelta = 1.0 - cosineSimilarity(current.embedding, previous.embedding);
|
|
55
81
|
const drift = this.history.length >= 4
|
|
56
82
|
? this.computeIntentState().drift_rate
|
|
57
83
|
: 0;
|
|
@@ -60,8 +86,8 @@ export class ResolutionTracker {
|
|
|
60
86
|
const lengthRatio = previous.text.length > 0
|
|
61
87
|
? Math.abs(current.text.length - previous.text.length) / previous.text.length
|
|
62
88
|
: 0;
|
|
63
|
-
const pivotScore = drift * 0.35 + repeatRatio * 0.
|
|
64
|
-
return pivotScore > 0.
|
|
89
|
+
const pivotScore = drift * 0.2 + embeddingDelta * 0.35 + repeatRatio * 0.1 + instructionChange * 0.15 + lengthRatio * 0.2;
|
|
90
|
+
return pivotScore > 0.4;
|
|
65
91
|
}
|
|
66
92
|
computeState() {
|
|
67
93
|
const n = this.history.length;
|
|
@@ -86,6 +112,7 @@ export class ResolutionTracker {
|
|
|
86
112
|
const entropyTrend = this.calcEntropyTrend();
|
|
87
113
|
const featureContradiction = this.calcFeatureContradiction();
|
|
88
114
|
const embeddingDelta = this.calcEmbeddingDelta();
|
|
115
|
+
const repeatStreak = this.getRepeatStreak();
|
|
89
116
|
const isLooping = this.detectLoop();
|
|
90
117
|
const intentState = this.computeIntentState();
|
|
91
118
|
const continuityState = this.continuityState(intentState);
|
|
@@ -131,9 +158,9 @@ export class ResolutionTracker {
|
|
|
131
158
|
const momentum = this.calcMomentum(entropyTrend, actionConsistency, embeddingDelta, isLooping, lastEntry.action, lastEntry.entropy);
|
|
132
159
|
let loopLevel = "none";
|
|
133
160
|
if (isLooping) {
|
|
134
|
-
if (this.loopCount >= 4)
|
|
161
|
+
if (repeatStreak >= 3 || this.loopCount >= 4)
|
|
135
162
|
loopLevel = "escalated";
|
|
136
|
-
else if (this.loopCount >= 3)
|
|
163
|
+
else if (repeatStreak >= 2 || this.loopCount >= 3)
|
|
137
164
|
loopLevel = "assertive";
|
|
138
165
|
else if (this.loopCount >= 2)
|
|
139
166
|
loopLevel = "suggestive";
|
|
@@ -161,6 +188,7 @@ export class ResolutionTracker {
|
|
|
161
188
|
continuity_state: continuityState,
|
|
162
189
|
is_looping: isLooping,
|
|
163
190
|
loop_consecutive: this.loopCount,
|
|
191
|
+
repeat_streak: repeatStreak,
|
|
164
192
|
loop_intervention_level: loopLevel,
|
|
165
193
|
pivot_detected: pivotDetected,
|
|
166
194
|
pivot_score: Math.round(pivotScore * 10000) / 10000,
|
|
@@ -220,10 +248,13 @@ export class ResolutionTracker {
|
|
|
220
248
|
detectLoop(k = 3, threshold = 0.6) {
|
|
221
249
|
const effectiveThreshold = this.calibratedWeights?.loopJaccard ?? threshold;
|
|
222
250
|
const effectiveK = this.calibratedWeights?.loopK ?? k;
|
|
251
|
+
const repeatStreak = this.getRepeatStreak();
|
|
252
|
+
if (repeatStreak >= 2)
|
|
253
|
+
return true;
|
|
223
254
|
if (this.history.length < effectiveK + 1)
|
|
224
255
|
return false;
|
|
225
|
-
const currWords = new Set(this.history[this.history.length - 1].text
|
|
226
|
-
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));
|
|
227
258
|
if (currWords.size === 0 || pastWords.size === 0)
|
|
228
259
|
return false;
|
|
229
260
|
const intersection = new Set([...currWords].filter(w => pastWords.has(w)));
|
|
@@ -349,19 +380,19 @@ export class ResolutionTracker {
|
|
|
349
380
|
return null;
|
|
350
381
|
const interventions = {
|
|
351
382
|
gentle: {
|
|
352
|
-
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.",
|
|
353
384
|
resetSuggested: false,
|
|
354
385
|
},
|
|
355
386
|
suggestive: {
|
|
356
|
-
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.",
|
|
357
388
|
resetSuggested: false,
|
|
358
389
|
},
|
|
359
390
|
assertive: {
|
|
360
|
-
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.",
|
|
361
392
|
resetSuggested: false,
|
|
362
393
|
},
|
|
363
394
|
escalated: {
|
|
364
|
-
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.",
|
|
365
396
|
resetSuggested: true,
|
|
366
397
|
},
|
|
367
398
|
};
|
|
@@ -144,7 +144,7 @@ function getStateFile() {
|
|
|
144
144
|
return join(getVibeOSHome(), "delegation-state.json");
|
|
145
145
|
}
|
|
146
146
|
function getFlowTodoFile() {
|
|
147
|
-
return join(getVibeOSHome(), "flow-todo-queue.jsonl");
|
|
147
|
+
return join(getVibeOSHome(), ".flow-todo-queue.jsonl");
|
|
148
148
|
}
|
|
149
149
|
const FLOW_DEDUP_FILE = join(getVibeOSHome(), ".flow-dedup-keys.json");
|
|
150
150
|
const MAX_FLOW_TODOS = 200;
|
|
@@ -295,8 +295,8 @@ export function getFlowWarns() {
|
|
|
295
295
|
}
|
|
296
296
|
export function getSessionFlowCounts() {
|
|
297
297
|
const counts = { warn: 0, hint: 0, flag: 0 };
|
|
298
|
+
const rules = loadRules();
|
|
298
299
|
for (const key of _flowWarnsSeen) {
|
|
299
|
-
const rules = loadRules();
|
|
300
300
|
const [ruleId] = key.split("::");
|
|
301
301
|
const rule = rules.find((r) => r.id === ruleId);
|
|
302
302
|
if (rule && counts[rule.severity] !== undefined)
|
|
@@ -369,7 +369,7 @@ export function recordFlowTodo({ filePath, content }) {
|
|
|
369
369
|
}
|
|
370
370
|
}
|
|
371
371
|
catch { }
|
|
372
|
-
console.error(`[flow-enforcer] 📋 Extracted ${todos.length} TODO(s) from ${filePath} → flow-todo-queue.jsonl`);
|
|
372
|
+
console.error(`[flow-enforcer] 📋 Extracted ${todos.length} TODO(s) from ${filePath} → .flow-todo-queue.jsonl`);
|
|
373
373
|
return todos.length;
|
|
374
374
|
}
|
|
375
375
|
catch {
|